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

Minecraft Data Packs: A Complete Guide

Building anything in Java Edition with data packs — from your first command to custom worlds

Java Edition 26.2


About This Book

Modern Minecraft is a data-driven game: items, recipes, enchantments, world generation, even mob variants are defined by JSON files the game reads on load. A data pack is how you add your own files to that system. This book gets you there the way the game itself does: you start by typing commands and watching them work, wire them into command blocks, then learn to save and share them as data packs, building project by project all the way to custom dimensions and world generation.

It assumes no prior programming experience and is written for readers aged 12 and up. Parts I–IX are the core path, taught in order. Parts X–XI are advanced material you can approach à la carte once the core is comfortable, and Part XII covers polishing and sharing what you’ve made.

Everything is hands-on and every example traces to current Java Edition behavior: you begin in the chat box on the very first page and end with data packs you can share.

Note. Screenshots are still being captured; figures appear as “Figure (to be captured)” callouts for now. Want to help capture them? See how to contribute.

Chapter 1 — Your First Commands

What You’ll Build

This is the most important five minutes in the whole book, because it’s the moment Minecraft stops feeling like a finished thing you play and starts feeling like a thing you can talk to. You’re going to open a world, type a short instruction into the chat box, press Enter, and watch the game do exactly what you said: hand you a diamond sword, drop a cow at your feet, whisk you across the map. No files, no setup beyond ticking one box. Just you and the game, having a conversation.

By the end of this chapter you’ll know the handful of commands that do the most useful “just make something happen” jobs: making the game talk (/say), handing yourself items (/give), spawning mobs and other entities (/summon), and moving things around (/teleport, which you can also write /tp). You’ll learn to read the little feedback line a command prints, so you can tell “it worked” from “it failed,” and you’ll meet the three simplest ways to point a command at someone: @s, @p, and @a. Everything in this chapter is typed straight into the chat box and happens the instant you press Enter.

Setting up a world to play in

Commands are powerful, so Minecraft only lets you use them in a world where cheats are turned on. (“Cheats” is just Minecraft’s word for “commands allowed.” There’s nothing sneaky about it; it’s the switch that hands you the controls.) Make yourself a world to experiment in:

  1. Click Create New World.
  2. Set the Game Mode to Creative (you get every block, you can fly, and nothing can kill you, perfect for tinkering).
  3. Open the More tab and turn Allow Cheats on.
  4. Create the world and drop in.

That’s your test world: a safe sandbox where a mistake costs you nothing. Do all your experimenting here, not in a survival world you care about. (When you start building real projects later in the book, you’ll keep using a world set up exactly like this one.)

Try It! Press T to open the chat box. A line opens at the bottom of the screen with a / already waiting. Type time set day and press Enter. If it’s night, the sun jumps up. You just ran your first command. Figure (to be captured). the chat box open with /time set day typed in, ready to press Enter

Concepts

A command is a single instruction to the game: “give this player a sword,” “spawn a zombie here,” “send everyone a message.” You open the chat box with T, and every command starts with a slash (/). The slash is how the game knows you’re giving an order instead of just chatting. Type the command, press Enter, and it happens immediately.

Most commands need to know who or what they act on. Instead of typing a player’s exact name every time, Minecraft gives you target selectors: short codes that stand for “some players or entities.” This chapter uses only the three simplest ones:

  • @s is yourself, the one running the command. When you type a command in chat, @s is you.
  • @p is the nearest player to where the command runs.
  • @a is all players online.

(There are more, like @e for all entities, @r for a random one, and @n for the nearest entity, but they come with filters and fine print that we save for Chapter 3. Three is plenty for now.)

Finally, almost every command prints a line of feedback after it runs: a short message telling you it worked, or a red message telling you it didn’t and roughly why. Learning to glance at that line is the fastest debugging habit there is, and we’ll lean on it at the end of the chapter.

Modern Minecraft Typing commands one at a time in chat is the fastest way to learn them, and that’s exactly what this chapter is for. Later you’ll discover two ways to save commands so you don’t retype them: putting them in a command block inside the world (Chapter 6), and saving whole lists of them in a data pack (Part III), which is how real creations are built and shared. For now, the chat box is your workbench.

Making the game talk: /say

The simplest “make something happen” command is /say. Its job: broadcast a plain-text message in the chat to everyone in the world. The syntax is just:

/say <message>

The <message> is everything after the word say. You don’t put it in quotes, you just type it. Open the chat box and try:

/say Welcome to my world!

Press Enter, and everyone in the world (in single-player, that’s you) sees [<your name>] Welcome to my world! in chat. That’s /say in full: plain, unstyled, broadcast to all.

/say is great for a quick “did that work?” check. The text is always plain, though: you can’t color it or make it clickable. When you want a styled, colored, or clickable message, there’s a fancier command called /tellraw. It’s genuinely useful, but it needs you to write the message in a small data format, and that format deserves a proper, gentle introduction rather than a surprise here on page one. So /tellraw gets its own chapter (Chapter 5), once you’ve got a few easy commands under your belt. For now, /say is all you need to make the game talk.

Figure (to be captured). chat showing a /say line broadcast to everyone, with the player-name prefix visible

Giving yourself items: /give

/give puts items straight into a player’s inventory. The syntax is:

/give <targets> <item> [<count>]

  • <targets>: who gets the item (@s = you, @p = nearest player, @a = everyone).
  • <item>: the item’s identifier, like minecraft:diamond (the minecraft: part can be left off for vanilla items, so diamond works too).
  • [<count>]: optional how many. The square brackets mean “you can leave this out”; if you do, it defaults to 1.

Try giving yourself a few things. Type these one at a time, pressing Enter after each:

/give @s diamond_sword /give @s diamond_pickaxe /give @s cooked_beef 16 /give @s oak_planks 64

The first two leave out the count, so you get one of each. The last two ask for 16 steaks and a full stack of 64 planks. Your inventory fills up as you press Enter.

What Went Wrong? “Nothing went into my inventory.” Two usual causes. First, your inventory might be full: in Creative the extra is simply discarded; in Survival it drops on the ground. Second, you may have misspelled the item ID (diamnod_sword). A wrong ID makes the command fail with a red feedback line; read it (see “Reading feedback” below).

Under the Hood /give items can carry extra built-in data in square brackets after the ID (for example a custom name, a color, or enchantments), written like diamond_sword[...]. That square-bracket syntax is the data component system, and it’s the whole of Chapter 21. You don’t need it yet; plain item IDs are all this chapter uses. (Skippable.)

Spawning entities: /summon

/summon creates an entity: a mob, a dropped item, a lightning bolt, anything that moves or lives in the world. The syntax is:

/summon <entity> [<pos>] [<nbt>]

  • <entity>: what to spawn, by identifier: minecraft:cow, minecraft:zombie, minecraft:lightning_bolt. (One catch: you can’t summon a player or a fishing bobber; those always fail.)
  • [<pos>]: optional where to spawn it. The brackets mean you can leave it out, and if you do, the entity spawns right where you’re standing. That’s perfect for us right now.
  • [<nbt>]: optional extra data describing the entity (its name, whether it’s a baby, effects on it, and so on). That’s a more advanced topic; we’ll spawn plain default mobs here.

Stand in an open spot, open chat, and try:

/summon cow /summon cow /summon zombie

Each press of Enter pops a mob into existence right on top of you: two cows and a zombie.

What Went Wrong? “I tried to put the mob at a specific spot and it failed / went somewhere weird.” To place a summon at an exact place you give it coordinates, and coordinates are the whole of the next chapter (Chapter 2). Until you’ve read it, the easy path is to leave the position out, which spawns the mob right where you stand. Walk to where you want it and summon there.

What Went Wrong? “Summoning a hostile mob did nothing.” On Peaceful difficulty the game refuses to summon hostile mobs like zombies. Open your world to Easy (or harder) and try again.

Moving things around: /tp and /teleport

To move a player or entity, use /teleport. It has a short alias, /tp, and they’re the same command, so tp and teleport do exactly the same thing; pick whichever you like to type. The two forms you’ll use most are:

/teleport <targets> <location> moves the target(s) to a set of coordinates. /teleport <targets> <destination> moves the target(s) to another entity’s position.

(If you give just one argument, /teleport <location> or /teleport <destination>, it moves you.)

A couple of examples to try:

  • /tp @a @s teleports all players to you (@s). The first selector is who moves, the second is where they go (here, to you). In single-player this just keeps you put, a safe way to watch the command parse without error.
  • Teleporting to exact coordinates is also possible, but coordinates are the next chapter; for now, teleporting to an entity (like @s) is the form to play with.

What Went Wrong? “Teleport sent me somewhere strange.” If you tried typing raw numbers, the order is always X Y Z, and a common mix-up is putting the height (Y) in the wrong slot, sending you underground or into the sky. Coordinates get their own chapter next (Chapter 2); until then, teleport to an entity rather than to numbers.

Reading feedback messages

Every command you run prints a small feedback line, and reading it is how you tell success from failure without guessing:

  • On success, the game prints a confirming line. For example, after a /give it tells you the item was handed over; after a /summon it confirms something was created.
  • On failure, the game prints a red line that names the problem: an argument that wasn’t filled in correctly, a target that matched nobody, a position the game couldn’t use.

A few common failures worth knowing as a checklist when a red line appears:

  • /give fails if the targets don’t resolve to any online player, or the item can’t be given.
  • /summon fails if you try to summon a player/fishing bobber, the spot is in an unloaded chunk, you summon a hostile mob on Peaceful, or the coordinates are out of the world’s huge legal range.
  • /teleport fails if the targets or the destination entity don’t resolve, or the coordinates are out of range.

So the routine is always the same: run the command, glance at the feedback line, and if it’s red, match it against the command’s failure reasons. The exact wording changes from command to command, so rather than memorize a message string, read the actual words your game prints and map them to a cause.

Common mistakes

A quick field guide to the beginner trip-ups this chapter tends to produce:

  • Forgetting the /. In the chat box, a command must start with a slash. Without it, the game thinks you’re just chatting and prints your text as a message instead of running it.
  • Forgetting the target on /give or /teleport. Unlike /say (which always goes to everyone), these need you to say who: @s, @p, or @a. Leaving it out is an error.
  • Misspelling an identifier. zombei, diamnod_sword, coww: a wrong ID fails the command with a red line. Type IDs carefully.
  • Trying to summon hostiles on Peaceful. The command quietly fails. Set your world to Easy or harder.

Practice

Build a one-shot “test playground” by typing a sequence of commands: announce it, kit yourself out, and spawn a few mobs to experiment on. Stand in an open area in your test world and type these in order:

/say Setting up the test area... /give @s diamond_sword /give @s diamond_pickaxe /give @s bread 16 /give @s torch 64 /summon cow /summon pig /summon zombie /summon skeleton /say Test area ready!

You should get your kit, see four mobs appear, and read the “setting up” and “ready” messages in chat.

Figure (to be captured). the test world right after running the setup sequence — the player holding the kit, with a cow, pig, zombie, and skeleton spawned nearby, and the setup messages in chat

Typing all ten lines every time you want a fresh playground gets old fast, and that nagging feeling is exactly the itch the rest of the book scratches. In Chapter 6 you’ll wire a sequence like this into a command block so a button press runs it, and in Part III you’ll save it as a function you can trigger with a single command. For now, just notice the wish: “I’d love to run all of these at once.” Hold onto it.

Now make the playground yours:

  1. Personalize the kit. Swap in items you like to test with, maybe bow and arrow 32, or a stack of tnt. Read each /give’s feedback to confirm it worked.
  2. Pick your mobs. Replace one mob with another you want to experiment on (creeper, villager, armor_stand). Remember hostiles need a non-Peaceful difficulty.
  3. Break one on purpose. Misspell an item or a mob (try /give @s diamnod_sword or /summon coww) and read the red feedback line it prints. Getting comfortable reading that red line now, when the mistake is harmless, is the single most useful habit you’ll carry through the whole book.

Try It! Here’s a wish you can’t grant yet: cleaning the playground back up. Removing only certain entities (say, just the zombies) needs target-selector filters like @e[type=zombie]. That’s Chapter 3, a good reason to keep reading.

What Can Go Wrong

  • Nothing happens and my text shows up in chat. You forgot the leading /, so the game treated your command as a chat message. Open chat, start with the slash, try again.
  • A command runs but seems to do nothing. Read its feedback line. A red line means it failed: match it to the command’s failure reasons (wrong ID, no matching target, Peaceful difficulty, a position the game can’t use). No red line plus no visible effect often means you weren’t looking where it happened (a mob summoned behind you, an item that overflowed a full inventory).
  • Mobs spawn in the wrong place, or not at all. With no position, a /summon spawns where you stand, so face an open area first. Precise placement needs coordinates, which is the very next chapter.

What You Know Now

You can open the chat box and talk to Minecraft: make it broadcast with /say, give items with /give (with an optional count), summon entities with /summon (spawning where you stand when you omit the position), and teleport players or entities with /teleport / /tp. You can aim any of them at @s (yourself), @p (nearest player), or @a (all players), and you can read a command’s feedback line to tell success from a red failure. You also felt the first real itch of this whole book (“I wish I could run all of these at once”), which is the thread everything else pulls on. Next, in Chapter 2, you’ll unlock real positioning power: the X/Y/Z axes and the ~ and ^ coordinate shortcuts that let your commands aim anywhere in the world.

Chapter 2 — Coordinates and Building

What You’ll Build

So far your commands have talked to the game (/say), handed you items (/give), and spawned mobs (/summon). This chapter teaches your commands to shape the world. By the end you’ll be able to type a short sequence that builds a small house out of stone, hollows out the inside, cuts a doorway, lays a floor, decorates it, and finishes with a puff of particles and a little chime. To get there you first need the game’s address system: coordinates, the numbers that name a spot in the world. You’ll learn the three directions the world runs in, how to read your own position off the F3 screen, and three different ways to write a position: one exact, one measured from where you stand, and one measured from where you’re looking. Then you’ll meet the five “world-shaping” commands: /setblock, /fill, /clone, /particle, and /playsound.

Everything happens in your test world (Creative with cheats), the same one you’ve used since Chapter 1.

The three axes: how the world is laid out

Every spot in a Minecraft dimension has an address made of three numbers, written in the order X Y Z. Each number is a distance along one axis, an invisible measuring line that runs through the world. The three axes cross at one spot called the world origin, the place where all three numbers are zero (0 0 0). One step of any axis is exactly one block (one cubic meter), so the numbers are just “how many blocks from the origin.”

Here’s what each axis measures:

  • The X-axis runs east–west. Travel east and your X number goes up (positive); travel west and it goes down (negative).
  • The Z-axis runs north–south. Travel south and your Z number goes up (positive); travel north and it goes down (negative).
  • The Y-axis runs up–down. Climb up and your Y number goes up; go down and it goes down. In the Overworld, Y ranges from -64 at the bottom of the world to 320 at the top, and sea level is Y=63.

So a position like 100 70 -40 means “100 blocks east of the origin, 70 blocks up, and 40 blocks north of the origin.” When you’re reading a set of coordinates, the trick is just to remember the order, X, then Y (the height), then Z, and that the middle number is always the height.

What Went Wrong? “I thought north was the positive direction, like ‘up’ on a graph.” A very natural guess, and a wrong one. Minecraft’s map is turned a quarter-turn from the math-class graph you may have seen: going north makes Z smaller (negative), not bigger. If a build keeps appearing on the wrong side of you, you’ve probably got a Z sign backwards. Face one direction, check F3, and walk a few blocks to see which way the number moves.

Under the Hood Your coordinates are actually the spot at the center of the bottom of your body. When F3 says you’re at Y=63, your feet are at 63.0 and your eyes are a bit higher (about 64.62). You don’t need this number for anything yet. It just explains why “your” Y and the block you’re standing on can differ by a hair. (Skippable.)

Reading your position with F3

To see your own position, press F3 (or Fn+F3 on a Mac or some laptops) to open the F3 debug screen, the overlay of technical text. Look at the upper-left: it shows your current coordinates (your XYZ) and which way you’re facing. It also lists your block position, your coordinates rounded down to whole numbers, which is the actual block you’re standing in (more on that just below).

The F3 screen also swaps your crosshair for a tiny set of colored arrows so you can see which way each axis points: +X is red (east), +Y is green (up), and +Z is blue (south). Whenever you lose track of which way is which, glance at that crosshair.

Figure (to be captured). F3 debug screen with the XYZ line and the colored +X red / +Y green / +Z blue crosshair annotated

Try It! Press F3, note your X, Y, and Z, then walk straight in one direction without turning. Watch which of the three numbers changes and whether it climbs or drops. You’ve just measured which axis you’re walking along. No memorizing required.

Block position: why coordinates round down

Your position can be a decimal, because you stand between whole blocks, but a block itself sits on the whole-number grid. The block position is the coordinates of the lower-northwest corner of a block: the whole numbers you get by rounding the decimal coordinates down. That’s the number the block-placing commands in this chapter use.

One quirk worth knowing because it bites people: rounding down behaves differently on each side of zero. For positive coordinates the block position starts at 0 (so the block from 0.0 up to 0.999… is block 0). For negative coordinates it starts at -1 (the block from -1.0 up to -0.001 is block -1). You don’t have to do this math by hand (F3 prints the block position for you), but it explains why a build can land one block off when you’re working in negative territory.

Three ways to write a position

Every command that takes a position will accept it in any of three styles. Learning all three now means the rest of the chapter (and the rest of the book) just works.

Absolute coordinates: the exact spot

Absolute coordinates are the plain numbers straight off F3: X Y Z measured from the world origin. 100 70 -40 always means that one exact spot in the world, no matter who runs the command or where they’re standing. Use absolute coordinates when you want something to appear in a fixed place: a build that always lands at the same spot.

Relative coordinates with ~ (tilde): “from where I am”

Often you don’t care about the exact spot. You want “right here” or “ten blocks that way from me.” That’s what relative coordinates are for. You write them in tilde notation: a ~ (tilde) before each number means “offset from the command’s current position along that axis.” A number after the tilde is the offset; a lone ~ with no number means an offset of 0 (i.e. “don’t move on this axis”).

  • ~ ~ ~ means the command’s current position, exactly here.
  • ~10 ~ ~-30 means 10 blocks east (+X) and 30 blocks north (–Z) of here, same height.
  • ~ ~5 ~ means 5 blocks straight up from here.

You can even mix tildes with absolute numbers. For example, a teleport to ~ 64 ~ keeps your X and Z exactly where they are but sets your height to an absolute Y of 64.

Relative coordinates are the workhorse of building: type a command with ~ coordinates and it acts around wherever you’re standing right now, instead of one hard-coded spot.

Local coordinates with ^ (caret): “from where I’m looking”

The third style measures from your facing direction instead of the world’s compass. Local coordinates use caret notation: a ^ (caret) before each number. The three carets mean, in order, sideways, up, and forward relative to the way the executor is looking:

  • The first ^ is left/right (left is positive),
  • the second ^ is up/down (up is positive),
  • the third ^ is forward/back (forward, the way you face, is positive).

So ^ ^ ^5 means “5 blocks straight ahead of where I’m looking,” wherever that is. Turn around and run it again and you end up back where you started, because “forward” flipped with you. This is perfect for “place something right in front of the player” effects.

Two rules to keep straight:

  • You can’t mix carets and world coordinates in the same position. Writing something like ^ 0 ^ fails: the game tells you “Cannot mix world & local coordinates.” A position is all carets or no carets.
  • When you have rotation 0 0 (looking due south, level), your local frame happens to line up with the world frame. A handy sanity check.

Under the Hood Pressing F3+B draws a blue ray out of every entity’s head showing its +Z-local (“forward”) direction. If you ever want to see which way “forward” points for a mob, that’s the toggle. (Skippable.)

Modern Minecraft If you’ve watched older build tutorials, you may have only ever seen absolute numbers typed into chat. Relative and local coordinates (~ and ^) are more powerful precisely because the same command works for any player at any location, well beyond the one spot you happened to test it. Think in relative and local coordinates and your builds become reusable: when you later save these commands into command blocks (Chapter 6) and functions (Part III), they’ll already work wherever they run.

/setblock: placing one block

The simplest building command places a single block. Its shape is:

setblock <pos> <block> [destroy|keep|replace|strict]

<pos> is a position (absolute, ~, or ^), <block> is the block to place, and the last word is an optional mode that says how to treat whatever block was already there. The modes are:

  • replace: swap in the new block; the old block drops neither itself nor its contents, and no break sound plays. This is the default if you leave the mode off.
  • destroy: the old block is broken as if a player mined it: it drops itself and its contents, and the breaking sound plays.
  • keep: only place into air; if there’s already a non-air block there, leave it alone.
  • strict: place the block as-is without triggering block updates. (You won’t need this until much later; it’s listed here only so the option isn’t a mystery when you see it.)

Here’s a command that drops a block of gold one space in front of where you’re looking:

/setblock ^ ^ ^1 minecraft:gold_block

Some blocks carry extra settings called block states, written in square brackets right after the block name (we’ll use them as we build from here on). For example, to place a chest already facing east at your feet:

/setblock ~ ~ ~ minecraft:chest[facing=east]

What Went Wrong?/setblock says it failed and nothing happened.” A few honest reasons: the target spot is outside the loaded world; or you tried to place the same block that’s already there (the game skips a pointless no-op); or with keep mode the spot wasn’t air. None of these are bugs. Read the red message and adjust.

/fill: building whole regions at once

/setblock does one block; /fill does a whole rectangular box of them in a single command. You give it two opposite corners and it fills everything in between. Its shape is:

fill <from> <to> <block> [outline|hollow|destroy|strict|replace|keep]
fill <from> <to> <block> replace <filter> [outline|hollow|destroy|strict]

<from> and <to> are any two opposite corners of the box (it doesn’t matter which is which: 0 0 0 to 5 5 5 fills the same box as 5 5 5 to 0 0 0). <block> is what to fill with. The optional mode says how to handle the blocks already there:

  • replace: replace every block in the box (including air) with your block, dropping nothing. This is the default.
  • destroy: replace everything, but drop the old blocks and their contents as items, as if mined.
  • hollow: fill only the outer shell of the box with your block and turn the inside to air. Great for making a room. (If the box is too small to have an inside, it acts like replace.)
  • outline: fill only the outer shell with your block, but leave the inside untouched. Like hollow, but it doesn’t clear what’s already inside.
  • keep: fill only the air blocks in the box, leaving existing blocks alone.
  • strict: place blocks as-is without triggering block updates (an advanced option you can ignore for now).

There’s also a second form with a filter: fill <from> <to> <block> replace <filter> changes only the blocks that match <filter>, leaving the rest as they are. For example, in a 20x10x20 box near you, this turns only orange glazed terracotta into gold, leaving everything else:

/fill ~ ~ ~ ~19 ~9 ~19 minecraft:gold_block replace minecraft:orange_glazed_terracotta

A few worked examples straight from the way /fill is meant to be used. A solid 7x3x7 pool of water just below you:

/fill ~-3 ~-3 ~-3 ~3 ~-1 ~3 minecraft:water

A hollow stone box around you (a house-sized shell, inside cleared to air):

/fill ~-3 ~ ~-4 ~3 ~4 ~4 minecraft:stone hollow

A solid 31x31x31 cube of stone centered on you:

/fill ~-15 ~-15 ~-15 ~15 ~15 ~15 minecraft:stone

What Went Wrong?/fill did nothing on a giant region.” There’s a size cap on how many blocks one command may change, and over-large fills are rejected. There’s a hard ceiling of 655360 blocks, and a gamerule that controls the limit as well. So rather than memorize one exact gamerule value, just know a limit exists: if a huge /fill or /clone refuses to run, break it into smaller boxes. To see the current name of that gamerule on your version, type /gamerule in chat and read it off the suggestion list; the in-game message is always the authority.

/clone: copying a region

/clone copies a box of blocks from one place to another, perfect for duplicating a build. The Java shape you’ll use is:

clone <begin> <end> <destination> [strict] [replace|masked|filtered <filter>] [force|move|normal]

<begin> and <end> are the two opposite corners of the region to copy (the source), exactly like /fill’s corners. <destination> is the lower-northwest corner where the copy is placed. Then come two optional choices:

What to copy (the “mask”):

  • replace: copy all blocks, overwriting everything in the destination. This is the default.
  • masked: copy only the non-air blocks, so the destination’s existing blocks show through where the source had air.
  • filtered <filter>: copy only blocks that match <filter>.

How to treat the source (the “clone mode”):

  • normal: copy and leave the source alone. This is the default.
  • force: allow the copy even if the source and destination regions overlap.
  • move: copy, then replace the source region with air (it “moves” the build).

There’s also a strict option (place as-is, no block updates) you can ignore for now.

A plain copy looks like this. It copies a 5x5x5 box next to you and pastes it 10 blocks east:

/clone ~ ~ ~ ~4 ~4 ~4 ~10 ~ ~

What Went Wrong?/clone failed.” The usual causes: the source and destination overlap (use force if you meant to, or move the destination away); the region is too big (same size limit as /fill); or part of either region isn’t loaded. If you’re cloning right next to the original, overlap is almost always the culprit.

Under the Hood /clone can even copy between dimensions with from <dimension> and to <dimension> forms. That needs the idea of naming a dimension, which we don’t cover until Chapter 44. So for now, clone within one world. (Skippable.)

/particle: visual effects

/particle sprays visual effects (smoke, flames, hearts, sparkles) into the world. They’re purely cosmetic (no gameplay effect), which makes them perfect for marking a spot or celebrating a result. The basic shape is short:

particle <name> [<pos>]

<name> is the particle type (like minecraft:explosion_emitter or minecraft:white_smoke) and the optional <pos> is where to make it, defaulting to your position if you leave it off. So a single explosion ten blocks east of you (cosmetic only) is:

/particle minecraft:explosion_emitter ~10 ~ ~

There’s a longer form for finer control:

particle <name> <pos> <delta> <speed> <count> [force|normal] [<viewers>]

You can mostly read those four numbers as: <count> is how many particles to make, <delta> is how spread out they are around <pos> (bigger numbers = a wider cloud), and <speed> scales how they behave. There’s one neat special case: if <count> is 0, you get a single particle and <delta> becomes its motion (the direction it drifts), with <speed> as a multiplier. The trailing force|normal controls range: normal only shows to nearby players, force pushes the particle to players much farther away and shows it even on low particle settings.

One puff of smoke drifting downward (count is 0, so delta is its motion):

/particle minecraft:white_smoke ^ ^1 ^2 0 -1 0 0.1 0

A whole vertical cloud of smoke (count is 10, so delta is the spread):

/particle minecraft:white_smoke ^ ^1 ^2 0 -1 0 0.1 10

Under the Hood Local coordinates (^) are great for the position of a particle, but <delta> always gets converted to absolute directions internally, so putting ~ or ^ in the delta numbers doesn’t do anything special. Use plain numbers there. (Skippable.)

/playsound: sound effects

/playsound plays a sound for a player. Its Java shape has a lot of optional pieces, but most have sensible defaults:

playsound <sound> [<source>] [<targets>] [<pos>] [<volume>] [<pitch>] [<minVolume>]
  • <sound>: the sound event to play, like entity.pig.ambient. (A sound event is a named entry the game already knows about; one event can pick randomly from several actual sounds, which is why pigs don’t all sound identical. Making your own sound events is a resource-pack topic for Chapter 30; here we use built-in ones.)
  • <source>: the volume category the sound belongs to, which lets players mute it with the right audio slider. It must be one of: master, music, record, weather, block, hostile, neutral, player, ambient, voice, or ui. Defaults to master.
  • <targets>: who hears it. Defaults to the player who ran the command. (We’ll cover picking targets properly in Chapter 3; @s, “me”, is enough here.)
  • <pos>: where the sound comes from. <volume> sets how far it carries (1 ≈ a 16-block radius; bigger numbers widen the range). <pitch> raises or lowers it (above 1 = higher and faster, below 1 = lower and slower; values under 0.5 are treated as 0.5). <minVolume> is the volume for listeners outside the normal range, and defaults to 0.

A simple chime to yourself, a pig oink (source neutral, so the “Friendly Creatures” slider controls it):

/playsound entity.pig.ambient neutral @s

What Went Wrong?/playsound failed even though I’m standing right there.” Two common causes: the target isn’t a player (sounds play to players, not mobs), or nobody could actually hear it: if the listener is outside the volume’s range and you didn’t set a <minVolume> above 0, the command reports failure. Move closer, raise the volume, or set a small minVolume.

Walkthrough: building a house

Now the payoff. You’ll type a short sequence that builds a small stone house around you, using relative ~ coordinates so it appears wherever you’re standing, then decorates it and signals that it’s done. Stand in an open spot in your test world, open chat, and type these lines in order, pressing Enter after each. Each one is described just before it.

The shell: a hollow stone box, 7 wide, 5 tall, 7 deep, with its floor at your feet. hollow fills the outer walls/roof/floor and clears the inside to air:

/fill ~-3 ~ ~-3 ~3 ~4 ~3 minecraft:stone hollow

A wooden floor inside, one layer, just above your feet:

/fill ~-2 ~ ~-2 ~2 ~ ~2 minecraft:oak_planks

Cut a doorway in the south wall, two air blocks tall:

/setblock ~ ~1 ~3 minecraft:air /setblock ~ ~2 ~3 minecraft:air

A glowing block in the ceiling so the room isn’t dark:

/setblock ~ ~4 ~ minecraft:glowstone

A chest in the corner, facing into the room:

/setblock ~2 ~1 ~2 minecraft:chest[facing=west]

A celebratory puff of particles at the center of the room:

/particle minecraft:white_smoke ~ ~2 ~

And a sound so you know the build finished:

/playsound entity.pig.ambient neutral @s

Type those in order and a stone hut springs up around you, floored, lit, with a door and a chest, finished off with a puff of smoke and an oink.

Figure (to be captured). the finished stone house built around the player, with the doorway, glowstone ceiling light, and chest visible

Try It! Change minecraft:stone in the first line to a flashier block (minecraft:quartz_block, minecraft:oak_planks, even minecraft:glass for a greenhouse) and run the sequence again. One word changes the whole look of your house.

Want a second house next door? Stand in the same spot you built the first one from and type this to copy the house you’re standing in and paste an identical one to the east:

/clone ~-3 ~ ~-3 ~3 ~4 ~3 ~5 ~ ~-3

Because the source box (~-3 ~ ~-3 to ~3 ~4 ~3) is exactly the box you filled for the shell, this copies the whole hut. The destination corner ~5 ~ ~-3 puts the copy a few blocks to the east, clear of the original so the regions don’t overlap.

Practice

  1. Make it bigger. Retype the shell line, widening it from ~-3 ... ~3 to ~-5 ... ~5 and raising the roof from ~4 to ~6. Notice you have to widen the floor line to match, or you’ll have a stone rim around your wooden floor.
  2. A window with the filter. Type a line that turns the front wall’s middle blocks into glass using the replace <filter> form, e.g. /fill ~-1 ~2 ~3 ~1 ~3 ~3 minecraft:glass replace minecraft:stone. Because it filters on minecraft:stone, it only changes wall blocks, never your doorway air.
  3. Light it from the front. Type a line that places a torch-like block in front of you with local coordinates: /setblock ^ ^ ^2 minecraft:glowstone. Run it facing different directions and watch the block follow your gaze.
  4. Clone a row of houses. Build a house, then run the clone line twice from the same spot but change the destination each time (~5 ~ ~-3, then ~13 ~ ~-3) to line up three identical huts.
  5. Mark the center. Type a /particle line that drops minecraft:white_smoke particles at ~ ~3 ~ so you can spot the middle of a build at a glance.

What Can Go Wrong

  • Coordinate order mix-ups. It’s always X, Y, Z, and Y is the height. If your build is buried or floating, you almost certainly swapped a number into the wrong slot. F3 prints them in the same X-Y-Z order, so compare directly.
  • Mixing ^ with ~ or plain numbers. A single position must be all caret (^) or no caret. ^ ~2 ^ won’t parse: the game refuses it with a “can’t mix world & local coordinates” message. Pick one style per position.
  • Forgetting hollow and getting a solid block of stone. /fill ... minecraft:stone with no mode defaults to replace, which fills the box solid. If you wanted a room, you need the hollow mode (and remember it clears the inside to air: put your floor and furniture in after).
  • Region too large. Very big /fill or /clone commands are rejected by the block-modification limit. If a giant build silently does nothing, split it into smaller boxes.

What You Know Now

You can now describe the world’s address system: the X (east/west), Y (up/down), and Z (north/south) axes, the origin at 0 0 0, and the Overworld’s Y range from -64 to 320. And you can read your own position straight off the F3 screen. You can write any position three ways: absolute (exact, from the origin), relative with ~ (“from where I am,” a lone ~ meaning no offset on that axis), and local with ^ (“sideways, up, forward, from where I’m looking”). And you can shape the world from the chat box: place one block with /setblock (and its destroy/keep/replace modes), fill and hollow and outline regions with /fill (including the replace <filter> form), copy a build with /clone (replace/masked/filtered, force/move/normal), throw cosmetic /particle effects, and play a built-in sound with /playsound. Best of all, you’ve typed a sequence that constructs and decorates a whole house around you: the first time your commands have built something real in the world.

Chapter 3 — Target Selectors: Picking Who or What

What You’ll Build

Up to now, almost every command you’ve typed has acted on you, the player running it. But you’ll often want more than “just me.” You’ll want “every zombie near the player,” or “the closest other player,” or “all the armor stands I summoned.” This chapter teaches the tool that lets a command pick its victims without you ever typing a name: the target selector. By the end you’ll know the five selectors you’ll reach for every day (@s, @p, @a, @e, @r) and how to bolt on square-bracket filters to narrow a crowd down to exactly the entities you mean, like @e[type=minecraft:zombie,distance=..10] for “every zombie within ten blocks.” Then you’ll type a short sequence of commands that rounds up nearby mobs, teleports them to you, and slaps an effect on them. Selectors are the single most reused idea in the rest of this book: every command from here on picks its targets this way.

This chapter builds on the chat commands from Chapter 1 and the coordinate ideas from Chapter 2. Everything here is typed straight into the chat box in your test world.

The problem selectors solve

A target selector is a shorthand for picking players or entities in a command without naming them or knowing their hidden ID: you write a short code and the game figures out who matches. You’ve actually already met one: every time you typed @s in Chapters 1–2, that was a selector meaning “myself, the one running this command.” @s is just the simplest member of a small family.

Here’s why this matters. Imagine you want a command to heal every player on the server. You could try to type out everyone’s name, but names change, players join and leave, and a data pack can’t know them ahead of time. A selector sidesteps all of that. You write @a (“all players”) once, and it resolves to whoever happens to be online when the command runs. Selectors turn “I have to know who specifically” into “I describe the kind of target I want, and the game finds them.”

The five selector variables

The first part of a selector is its variable: the @-something that names the broad category of targets. There are five you’ll use constantly. (There’s actually a sixth in Java Edition, covered in the Under the Hood box below.)

  • @s — yourself. Selects the entity that the command was executed as, usually you. It picks the executor whether they’re alive or not. If a command wasn’t run as an entity at all (say, from the server console), @s selects nothing. This is the one you’ve been using.
  • @p — the nearest player. Selects the nearest player to where the command runs. If two players are tied for closest (exactly the same distance), the one who most recently joined the server wins.
  • @a — all players. Selects every online player, alive or dead.
  • @e — all entities. Selects all alive entities in loaded chunks, plus all alive online players. “Entity” here means everything that lives in the world: mobs, dropped items, armor stands, arrows, you name it, not just players.
  • @r — a random player. Selects a random online player.

A quick but important caution about @r: in Java Edition it picks a random player, not a random mob. If you want a random entity (say, one random zombie), write @e[sort=random,limit=1] instead. You’ll understand exactly what that means by the end of this chapter.

You can type any of these into chat on its own. Here are a few one-liners:

/say Hello from @s /effect give @a minecraft:glowing 10 0 /kill @e[type=minecraft:arrow]

The first makes you say hello. The second gives every online player ten seconds of Glowing. The third, using a filter you’ll meet shortly, removes every stray arrow in loaded chunks. (We’ll come back to effect give and how its arguments work when we build the practice function.)

Under the Hood Java Edition actually has six selector variables, not five. The sixth is @n, the nearest alive entity (any entity, not just a player, unlike @p). It works just like the others and you’re free to use it, but this book leans on the five above because they cover almost everything a beginner needs, and @n is easy to mimic with @e[limit=1,sort=nearest] once you’ve learned filters. Skip this box if it’s more than you want right now.

Modern Minecraft If you’ve watched older tutorials or Bedrock (phone/console) videos, you may have seen extra selectors like @c, @v, or @initiator. Those are specific to Bedrock Edition or the Education Edition. They don’t exist in the Java Edition this book teaches. Stick to the Java five (plus @n) and you’ll never be surprised.

Filtering: narrowing the crowd

A bare @e is a fire hose: it grabs everything. The real power of selectors is filter arguments (the wiki calls them “target selector arguments”): extra conditions you list in square brackets to keep only the targets that match. The shape is always the same: the variable, then square brackets holding argument=value pairs separated by commas:

@e[type=minecraft:zombie,distance=..10]

Read that as: “all entities, but only the ones that are zombies and within ten blocks.” A few rules worth burning in now:

  • Multiple filters are AND-ed together. Every pair must be true for a target to make the cut. The example above keeps something only if it’s both a zombie and close enough.
  • Filters change how @p, @r, and @s behave, too. With @a or @e, filters narrow the full list. With @p or @r, the nearest/random target is chosen from the filtered group. With @s, you’re kept only if you would land in that group, so @s[type=minecraft:zombie] selects nothing when you run it (you’re a player, not a zombie).
  • Case matters. In Java Edition, argument names and values are case-sensitive. type=Zombie won’t match minecraft:zombie.
  • No space before the first bracket. You can put spaces around the equals signs and commas if you like, but not between the variable and its opening [.

Let’s walk through the filters you’ll use most.

type= — filter by entity type

The type= argument keeps only entities of a given kind, named by their identifier (the namespace:path name you’ll meet properly in Chapter 8). The minecraft: namespace can be left off, so type=zombie and type=minecraft:zombie mean the same thing.

@e[type=minecraft:zombie]
@e[type=creeper]

Put a ! in front of the value to mean “everything except this type”:

@e[type=!minecraft:player]

That selects every entity that isn’t a player, handy for “all the mobs and items, but leave the people alone.” Two rules to be strict about:

  • A plain type=<something> (no !) can appear only once, and you can’t mix it with a ! exclusion. So @e[type=creeper,type=pig] is an invalid selector (an entity can’t be two types at once anyway). If you want creepers or pigs, that’s what entity tags are for: previewed below under tag=, taught fully in Chapter 13.
  • You can stack several exclusions: @e[type=!creeper,type=!pig] means “everything except creepers and pigs.”
  • You can’t use type= with @a, @p, or @r in Java Edition, because those already mean “players,” and a type filter would either be redundant or contradictory.

distance= — filter by range

The distance= argument keeps only targets within (or beyond) a distance of the point the command runs from. This is where you meet range syntax, a little notation Minecraft uses all over the place: two dots .. mean “a range,” and which side the number is on says whether it’s a maximum or a minimum:

@e[distance=..10]      all entities LESS than (up to) ten blocks away
@e[distance=10..]      all entities MORE than ten blocks away
@e[distance=8..16]     all entities between eight and sixteen blocks away (inclusive)
@e[distance=10]        all entities EXACTLY ten blocks away

So ..10 is “no more than 10,” 10.. is “at least 10,” and 8..16 is the band in between. Distances are measured to the target’s feet, and only positive (unsigned) values are allowed; there’s no such thing as a negative distance. Distance also limits the search to the dimension the command runs in.

You’ll use distance=..N constantly: “do something to everything near here” is one of the most common things a data pack ever asks for.

name= — filter by name

The name= argument keeps only targets whose name matches exactly. If the name has spaces in it, wrap it in quotes.

@e[name=Rover]
@e[name="Sir Barksalot"]
@a[name=!Steve]

The last one, with !, means “every player except the one named Steve.” Names are how you single out a specific mob you’ve named with a name tag, or a particular player. But remember names can change and aren’t unique, so they’re a blunt tool compared to the next one.

tag= — filter by a label you put on entities

The tag= argument keeps only entities carrying a particular scoreboard tag, a simple text label you can stick on any entity to mark it. You haven’t learned how to add tags yet (that’s the /tag command, coming in Chapter 13), but you’ll see tag= in selectors everywhere, so meet it now:

@e[tag=is_boss]
@e[tag=!frozen]

The first selects every entity wearing the is_boss label; the second selects everything without the frozen label. Tags are the cleanest way to mark “these specific entities are special”: for example, tagging the three zombies your pack summoned so a later command can find just those three and ignore every other zombie in the world. We’ll build real tag-based systems in Chapter 13. For now, just know that tag= reads a label, and ! flips it.

limit= and sort= — how many, and which ones

By default, @a and @e grab every match. Often you want only one, or only the closest few. Two arguments control that together:

  • limit=<number> caps how many targets come back.
  • sort=<order> decides which ones survive the cap, by setting the order before the limit applies. The four orders are:
    • sort=nearest: closest first (this is the default for @p)
    • sort=furthest: farthest first
    • sort=random: shuffled (this is the default for @r)
    • sort=arbitrary: no sorting; often returns the oldest entities first, but no order is promised (this is the default for @e and @a)

Examples:

@a[limit=3,sort=nearest]    the nearest three players (same as @p[limit=3])
@a[limit=4,sort=furthest]   the farthest four players
@a[limit=2,sort=random]     two players chosen at random (same as @r[limit=2])

Notice the last two lines of each pair: @p is really just “@a with limit 1, sorted nearest,” and @r is “@a with limit 1, sorted random.” That’s also the trick from earlier: @e[sort=random, limit=1] is how you grab one random entity (rather than a random player, which is what @r gives you).

nbt= — a quick preview

The nbt= argument filters by an entity’s NBT data, the raw saved data a mob or item carries internally (its health, whether a sheep is sheared, what color it is, and so on). You’ll learn what NBT actually is in Chapter 12; for now just know nbt= exists and what it looks like:

@a[nbt={OnGround:true}]

That selects all players standing on the ground. One warning to be explicit about: reading NBT is a heavy process for the CPU, so use nbt= sparingly. In fact, the wiki points out that @e[nbt={Tags:[a,b]}] does the same job as @e[tag=a,tag=b], and the tag= version is both simpler and lighter on the game. So reach for tag= first; save nbt= for things only NBT can express.

Modern Minecraft Tutorials made for Bedrock Edition use different argument names: r= and rm= for distance, c= for limit, m= for game mode, and so on. The Java Edition this book teaches uses the longer, readable names (distance=, limit=, sort=) with the .. range syntax. If a guide tells you to write @e[r=10], that’s Bedrock; the Java version is @e[distance=..10].

Try It! The selector page lists more filters than we’ve covered: by experience level=, by gamemode=, by facing direction (x_rotation= / y_rotation=), by a cuboid volume (dx= / dy= / dz=), and more. You don’t need them yet, but if you’re curious, every one follows the same argument=value shape and the same .. range and ! rules you just learned. Two more (scores= and predicate=) wait on ideas from later chapters (scoreboards in Chapter 11, predicates in Chapter 18).

Combining filters

Because filters AND together, you build precise targets by stacking them. This is the everyday craft of data packs. Read each of these as a sentence:

@e[type=minecraft:zombie,distance=..10]

“Every zombie within ten blocks.” (The chapter’s headline example.)

@e[type=minecraft:armor_stand,tag=marker]

“Every armor stand wearing the marker label.”

@a[distance=..16,limit=1,sort=nearest]

“The single nearest player within sixteen blocks.”

@e[type=!minecraft:player,distance=..5]

“Everything that isn’t a player, within five blocks of here.”

The order you write the filters in doesn’t matter. [type=...,distance=...] and [distance=...,type=...] mean the same thing, because all the conditions must be true together.

Walkthrough: rounding up nearby zombies

Time to put selectors to work. You’ll type a short sequence of commands that:

  1. finds every zombie within ten blocks of you,
  2. teleports those zombies to you, and
  3. gives them Glowing so you can see who got rounded up.

This combines a filtered selector (@e[type=...,distance=...]) with two commands you’ll meet here: /teleport and /effect give.

First, the two commands:

  • /teleport <targets> <destination> moves the targeted entities to a destination, which can be another entity. So /teleport @e[...] @s means “teleport those entities to me.” (/teleport is the full name of the /tp command you saw in Chapter 1; they’re the same command.)
  • /effect give <targets> <effect> [<seconds>] [<amplifier>] applies a status effect. The seconds and amplifier are optional; amplifier is the level minus one, so amplifier 0 is level I. If you leave seconds off it defaults to 30.

In your test world, summon a few zombies near you (you learned /summon in Chapter 1), then type these three commands in order, pressing Enter after each:

/teleport @e[type=minecraft:zombie,distance=..10] @s /effect give @e[type=minecraft:zombie,distance=..10] minecraft:glowing 15 0 /say Rounded up the nearby zombies!

The first teleports every zombie within ten blocks to you; the second gives those same zombies Glowing for fifteen seconds so you can spot them; the third announces what happened. A few things to notice:

  • The selector is identical on both lines. Each command re-runs the selector fresh, so both the teleport and the effect act on the same group: zombies that were within ten blocks. (After the teleport they’re standing on top of you, but the second line’s distance=..10 still includes them, since they’re at distance zero.)
  • @s is the destination. Because you’re typing these in chat, @s is you, so the zombies teleport to you.

The nearby zombies should snap to your position and start glowing. Any zombie farther than ten blocks away is left alone: that’s your distance=..10 filter doing its job.

Figure (to be captured). the player surrounded by glowing zombies right after running the round-up commands, with one un-glowing zombie visible in the distance that was outside the 10-block range

Under the Hood @s meaning “you” is doing quiet work here. A selector like @s only means “the player” if the command was run as that player, which it is when you type it in chat yourself, because you are the executor. In the next chapter you’ll learn the /execute command, which lets you deliberately change who @s is and where a command runs from: for example, “run this once as each zombie, at that zombie’s feet.” That’s how you’ll do per-entity work. For now, typing the command yourself keeps @s simple: it’s you.

Practice

Re-run the round-up commands and experiment with selectors:

  1. Wider net. Change both distance=..10 filters to distance=..20 and re-run. More zombies get caught. Then try distance=5..20: now zombies closer than five blocks are skipped. Predict who gets rounded up before you run it, then check.

  2. Round up something else. Type a command with the opposite mood: give every cow within fifteen blocks the Speed effect so they bolt. Use @e[type=minecraft:cow,distance=..15] and effect give ... minecraft:speed 10 2 (amplifier 2 is Speed III):

    /effect give @e[type=minecraft:cow,distance=..15] minecraft:speed 10 2 /say The cows have had too much coffee.

  3. Only the closest. Type a command that teleports only the single nearest zombie to you, not all of them. Hint: add limit=1,sort=nearest to the selector, @e[type=minecraft:zombie,distance=..10,limit=1,sort=nearest].

  4. Spare the named ones. Suppose some mobs are pets you’ve named. Add name=! filters or, better, plan ahead for Chapter 13 by imagining a tag=!pet filter that would skip any entity you’ve labelled pet. (You can’t add the tag yet, that’s Chapter 13, but you can already read it in a selector.)

What Can Go Wrong

What Went Wrong? “My selector grabbed nothing.” The most common cause is the AND rule: every filter has to be true at once. If you wrote @e[type=minecraft:zombie,distance=..2] and the nearest zombie is three blocks away, you get zero targets, not because the type is wrong, but because nothing satisfies both conditions. Loosen one filter at a time to find which one is excluding everyone.

What Went Wrong? “I got an error about my type argument.” Check three things. First, case: Java is case-sensitive, so it’s minecraft:zombie, never minecraft:Zombie. Second, you can only have one plain type= (no !) per selector; @e[type=zombie,type=pig] is invalid. Third, you can’t use type= with @a, @p, or @r at all, because those already mean “players”; use @e when you want to filter by entity type.

What Went Wrong? “Nothing happened, and there’s no error.” If a selector matches nobody, the command simply does nothing, quietly. That’s not a crash; it’s an empty target list. Run a harmless test like /say @e[type=minecraft:zombie,distance=..10] first: if it echoes the matched entities, your selector works and the problem is elsewhere; if it shows an empty result, your filter is too tight or the entities aren’t where you think.

What You Know Now

You can pick targets for any command without naming them. You know the five everyday selector variables: @s (yourself), @p (nearest player), @a (all players), @e (all entities), and @r (a random player), and that @e reaches everything in loaded chunks. You can attach filter arguments in square brackets and combine them, knowing they all have to be true at once: type= for entity kind (with ! to exclude, once-only for the plain form, and off-limits to @a/@p/@r), distance= with .. range syntax (..10, 10.., 8..16), name= for an exact name, tag= for a label (a preview of Chapter 13), and limit= with sort= (nearest/furthest/random/ arbitrary) to control how many and which. You’ve seen nbt= exists but is heavy and best avoided until Chapter 12. And you’ve typed a round-up sequence that filters, teleports, and buffs a precise group of entities: the pattern behind nearly every command you’ll write from here on.

Next chapter unlocks the command that makes selectors truly programmable: /execute, which lets you change who a command runs as and where it runs from, so you can do per-entity work like “as every zombie, at its position, strike lightning.”

Chapter 4 — The /execute Command

What You’ll Build

This is the most important command in Minecraft, and it’s the one that turns a list of commands into something that can actually think. So far, every command you’ve written runs as you, where you are, no matter what. /execute breaks all three of those rules. It lets you run a command as a different entity, run it at a different place, and run it only if something is true. By the end of this chapter you’ll be able to read a long /execute line left to right and say exactly what each piece does, and you’ll have typed two real /execute commands into the chat box: one that gives a player a potion effect while they stand on a gold block, and one that calls down lightning on every zombie at once. These two small lines use the same machinery that powers nearly every advanced data pack ever made.

This chapter assumes you’re comfortable with target selectors from Chapter 3 (@s, @e, @a, and filters like type= and distance=) and with coordinates from Chapter 2 (absolute, ~ relative, ^ local).

Why /execute exists

Every command secretly carries some hidden background information with it: who is running it, and where it’s running. The official description of the command says it plainly: /execute “executes another command but allows changing the executor, changing the position and angle it is executed at, adding preconditions, and storing its result.” Read that again, because the whole chapter is just those four powers spelled out:

  • change the executor: run a command as a different entity (this changes what @s means).
  • change the position: run a command at or positioned at a different place (this changes what ~ ~ ~ means).
  • add preconditions: run the command only if (or unless) some condition is true.
  • store its result: save a number the command produces (you’ll preview this here; the details come later).

Two pieces of vocabulary make the rest of the chapter easy. The executor is the entity a command runs as. It’s what @s (“self”) points at. The execution position is the point a command runs at, the spot relative coordinates like ~ ~ ~ are measured from. Plain commands always use you as the executor and your spot as the position. /execute is how you change either one.

A piece of /execute is called a subcommand (the game also calls them instructions). You chain subcommands together, and the very last one is always run, followed by the real command you want to carry out. Here’s the shape of every /execute line you’ll ever write:

/execute <subcommand> <subcommand> ... run <the actual command>

The subcommands fall into a few jobs: modifier subcommands change the context (who and where), condition subcommands test something (if/unless), a store subcommand saves a result, and the run subcommand carries out the real command at the end. We’ll meet one job at a time.

Modern Minecraft Older tutorials sometimes show a chat command typed straight into the box, like /execute @e ~ ~ ~ summon lightning_bolt. That old grammar was retired years ago. Current Java Edition uses the named-subcommand form you’re learning here (execute as @e at @s run summon lightning_bolt), which reads almost like a sentence. If a tutorial’s /execute looks like a string of bare selectors and coordinates with no words like as, at, or run, it’s out of date.

as — change who runs the command (the executor)

The as subcommand “sets the executor to target entity.” In plain terms: it changes who the command thinks it is, which changes what @s means. Its syntax is:

as <targets> -> execute

That little -> execute is shorthand for “another subcommand must follow”: as can’t be the end of the line. Here’s the classic example:

/execute as @e[type=sheep] run kill @s

Walk through it: as @e[type=sheep] makes the game pretend, one at a time, that it is each sheep. Then run kill @s kills “self,” and because “self” is now a sheep, every sheep dies. Without execute as, kill @s would just kill whoever typed the command.

This reveals something important. When a selector picks more than one entity, the rest of the chain runs once for each one. This is called forking into multiple branches: when the as subcommand selects multiple entities, the subcommands following it execute once per entity. This is how one line of /execute can act on a hundred mobs: you type the command once, and the fork runs it for each match.

at — change where the command runs (the position)

The as subcommand changes who, but, importantly, it does not change where. There’s an important detail here: as sets the executor without changing the execution position. So if you do execute as @e[type=sheep] run particle ..., every sheep is the executor, but the particle still appears at your feet, because the position never moved.

That’s what at is for. The at subcommand “sets the execution position, rotation, and dimension to match those of an entity.” Its syntax:

at <targets> -> execute

The two are almost always used together as as @e[...] at @s, which reads: “for each matching entity, become it (as), then move to its spot (at @s).” This example makes the pairing clear:

/execute as @e[type=sheep] at @s run tp @s ~ ~1 ~

This moves every sheep up one block. as @s makes each sheep the executor, at @s moves the position to that sheep, and tp @s ~ ~1 ~ teleports self one block above its current spot.

To see why order matters, compare these two lines:

  • /execute as @e at @s run tp ^ ^ ^1: all entities move one block forward.
  • /execute at @s as @e run tp ^ ^ ^1: all entities teleport to one block in front of the executor.

The game reads subcommands strictly left to right, so swapping as and at gives two completely different results. There’s also a trap worth memorizing: at never changes who the executor is. Watch this gotcha. /execute at @e[type=sheep] run kill @s kills the player running the command, because at does not change the executor. You moved the position to a sheep, but @s is still you.

positioned — set the position directly

at borrows a position from an entity. positioned lets you set the position yourself, with no entity needed: it sets the execution position directly. It has a few forms; the two you’ll use are:

positioned <pos> -> execute
positioned as <targets> -> execute

positioned <pos> takes coordinates, like positioned 0 64 0. This example searches for a village near a fixed point:

/execute positioned 0 64 0 run locate structure #village

positioned as @s, on the other hand, copies an entity’s position only. Unlike at, it leaves the rotation and dimension alone. For most beginner uses, at @s is what you want; reach for positioned when you need an exact coordinate or you only want the location, not the facing.

if / unless — run the command only when something is true

So far we can change who and where. The condition subcommands add whether. The if and unless subcommands restrict command execution to happen only under specified conditions. In most cases, unless is a negation of if, equivalent to “if not…”. When a condition fails, that branch simply stops (it “terminates”) and the run never happens.

if and unless come in several flavors. The two you’ll use constantly are if entity and if block.

if entity checks whether a matching entity exists. Syntax:

(if|unless) entity <entities> -> [execute]

The [execute] in brackets means another subcommand here is optional: if can be the last thing on the line, or it can be followed by run. So /execute if entity @e[type=creeper,distance=..10] run say A creeper is near! only says the message when a creeper is within 10 blocks.

if block “compares the block at a given position to a given block ID or a block tag.” Syntax:

(if|unless) block <pos> <block> -> [execute]

This is how you check what someone is standing on. This example kills any player standing on a wool block:

/execute as @a at @s if block ~ ~-1 ~ #wool run kill @s

Read it as a sentence: for each player (as @a), at their position (at @s), if the block one below them (~ ~-1 ~) is wool, kill them. The ~ ~-1 ~ means “same X and Z, one block down,” exactly the spot your feet rest on. You’ll reuse this exact pattern in the practice below, swapping wool for gold.

There are more condition types you’ll meet in later chapters. Three are worth naming now so the syntax doesn’t surprise you when an online tutorial uses it:

  • if score compares scoreboard numbers, e.g. if score @s wins matches 3... Scoreboards are a whole system of their own. You’ll learn them in Chapter 11, and if score with them.
  • if predicate checks a named, reusable condition you save as a JSON file. Predicates get their own Chapter 18.
  • if data checks whether an entity or block has a piece of data; the data system (command storage) is Chapter 12.

Try It! unless is just if flipped. Once you’ve typed the gold-block command below, try changing if block to unless block and stand off the gold — the effect now applies everywhere except on gold. Reading unless as “if not” makes these lines click.

store — saving a result (preview)

The fourth power, store, stores the final subcommand’s result or success value somewhere. A command quietly produces a number when it runs (for example, if entity @e[type=zombie] produces how many zombies matched), and store catches that number and saves it. There are five places it can save to (a block, a bossbar, an entity, a score, or a storage), with syntax like:

store (result|success) score <targets> <objective> -> execute

You don’t have the tools to use the saved number yet: scores live in Chapter 11 and command storage in Chapter 12, and the deeper store patterns wait for Chapter 27. For now, just recognize the word: when you see execute store result score ... in someone else’s pack, it means “run this and remember the number it gives back.” We’ll come back and wire it up properly later.

run — the final step

Every chain ends with run. Its single argument is the command to be executed, whose context variables may be modified by the subcommands used. In other words, run is where you put the actual command, and all the as/at/if pieces before it have already set up the who, where, and whether.

Two rules keep you out of trouble:

  • run can be used only once, at the very end. You can stack as many as, at, and if subcommands as you like, but run finishes the line.
  • A chain that doesn’t end in run is only legal if it ends in a condition (if/unless). Only a run subcommand or a condition subcommand may finalize the chain; otherwise, the command is unparseable. If you end on as or at with nothing after, the game rejects the line.

Walkthrough: building a chain one piece at a time

The best way to understand a long /execute is to grow it. Let’s build up a command that warns you about nearby zombies, subcommand by subcommand, typing each version into chat as we go.

Start with the plainest version, just a message:

/say Checking for zombies...

Now make it speak as each zombie:

/execute as @e[type=zombie] run say I am a zombie.

If three zombies are loaded, that line forks into three branches and you get the message three times, once per zombie, each running as that zombie. Finally, only warn when zombies are actually close, by adding a condition. The finished pair of commands:

/execute if entity @e[type=zombie,distance=..16] run say A zombie is within 16 blocks! /execute as @e[type=zombie,distance=..16] at @s run say A zombie stands here.

The first line uses if entity as the last subcommand-before-run: the message fires once, only when at least one zombie is within 16 blocks. The second line forks over every nearby zombie and runs as and at each one. Type each into chat and press Enter to try it.

Practice 1 — if a player stands on gold, give them an effect

Now the real thing. We want: for every player, check the block under their feet, and if it’s a gold block, give them a potion effect. This is the wool example from earlier with two swaps: wool becomes gold, and kill becomes effect give. The effect command’s syntax is effect give <targets> <effect> [<seconds>] [<amplifier>]. Place a gold block, stand on it, open chat, and type:

/execute as @a at @s if block ~ ~-1 ~ minecraft:gold_block run effect give @s minecraft:speed 2 1

Read it left to right: as @a (for each player) at @s (at that player’s spot) if block ~ ~-1 ~ minecraft:gold_block (if the block one below is a gold block) run effect give @s minecraft:speed 2 1 (give self Speed for 2 seconds at amplifier 1). Typed once in chat, it gives you a 2-second burst of Speed if you’re standing on gold. To make it a constant boost that refreshes every tick while you stand on the gold, you’ll later put this exact line in a repeating command block (Chapter 6) or in a function that runs every tick (Part III). For now, type it by hand to watch the condition work.

Figure (to be captured). player standing on a single gold block with the Speed effect icon showing in the corner

What Went Wrong? Effect won’t apply? The most common cause is the block ID. Make sure you’re testing for a block ID like minecraft:gold_block, not an item: a gold ingot is an item, not a block, so if block will never match it. Also check the offset is ~ ~-1 ~ (one below) and not ~ ~ ~ (the block you’re standing inside, which is air).

Practice 2 — as every zombie, at its position, summon lightning

This one is pure spectacle and shows off forking. We want to run, for every zombie, summon lightning_bolt at that zombie’s own position. The summon command (Chapter 1) is summon <entity> [<pos>]; with no position it summons at the execution position, which is exactly what at @s sets up. Summon a few zombies, then type:

/execute as @e[type=zombie] at @s run summon minecraft:lightning_bolt

as @e[type=zombie] forks over every zombie; at @s moves the position onto each one; and summon minecraft:lightning_bolt (with no coordinates) strikes at that position. Because the chain forks, one short line strikes every zombie at once.

Figure (to be captured). several zombies being struck by lightning at once, one bolt per zombie

Try It! Add a condition so only nearby zombies get hit: change the selector to @e[type=zombie,distance=..20]. Or make it rain effects instead of lightning — swap the run for run effect give @s minecraft:glowing 30 0 so every zombie lights up. Notice how only the last part (after run) changes; the as ... at @s scaffolding stays the same. That scaffolding is the reusable heart of /execute.

What Can Go Wrong

Forgetting at after as. This is the number-one beginner mistake. /execute as @e[type=zombie] run summon lightning_bolt makes each zombie the executor, but it never moves the position, so all the lightning strikes at your feet instead of at the zombies. Whenever you want something to happen where an entity is, you almost always need as <sel> at @s together, not as alone.

Putting run in the wrong place, or twice. run must be last, and may appear only once. A line like /execute run say hi as @e won’t work because nothing is allowed after the run command. And a chain that ends on a modifier with nothing after it (/execute as @a at @s) is “unparseable,” because only a run or an if/unless may finish a chain.

Mixing up @s and the position. Remember that as changes the executor (@s) and at/positioned change the position (~ ~ ~): they’re two separate things. If your command targets the wrong entity, check your as. If it happens in the wrong place, check your at. The bug is almost always one of those two, in the wrong order or missing entirely.

What You Know Now

You can read and write the most important command in the game. You know that /execute runs another command after changing the executor (as), the position (at, positioned), and the condition (if/unless), and that the chain always finishes with run plus the real command. You understand forking (that a multi-entity selector makes the rest of the chain run once per entity) and you’ve used it to act on every zombie at once. You’ve previewed store and the if score / if predicate / if data conditions, which unlock fully once you learn scoreboards (Chapter 11), command storage (Chapter 12), and predicates (Chapter 18). You’ve typed live /execute lines that warn about nearby zombies, boost players standing on gold, and call lightning down on every zombie at once. From here on, /execute shows up in almost every chapter. It’s the glue that holds programmable data packs together.

Chapter 5 — Text Components: Styled and Dynamic Text

What You’ll Build

Back in Chapter 1 you met /say for plain messages, and you were promised a fancier message command called /tellraw once you’d built up to it. This is that chapter. /tellraw sends a text component: a small piece of JSON that describes what a message says, how it looks (color, bold, italics), and what it does when a player clicks or hovers over it. Text components are how Minecraft does every bit of styled, interactive text: colored chat, clickable links, hover tooltips, and big titles on the screen.

By the end of this chapter you’ll be able to color and style text, drop in dynamic pieces that fill themselves in (a player’s name, a translated word, a score), make text clickable and hoverable, and send it all to players with /tellraw and /title. You’ll finish by building a styled, clickable welcome message and firing it off in chat to greet everyone in color.

Everything in this chapter is typed straight into the chat box of your test world, the same way you’ve been running commands since Chapter 1.

What a text component is

A text component (the wiki sometimes calls it “raw JSON text,” its older name) is the format Minecraft uses for any formatted text. They start small: in /tellraw @a {"text":"Hello"}, that {"text":"Hello"} part is a text component, the simplest one there is.

The simplest text component is just a string of text. These three are all the same message:

  • "Hello world": a plain string.
  • {"text":"Hello world"}: an object (a {} block) with one field, text.
  • ["Hello world"]: a list (a [] block) with one item in it.

So a text component can be written three ways: as a plain string, as a list, or as a compound object (the {} form). The string and list forms are just shorthand for the object form. Most of the time you’ll write the object form, because that’s the one you can attach color and clicks to.

A component can have children. There is always one root component at the top, and it can hold a list of more components in a field called extra. Here’s the key rule: children inherit the root’s formatting unless they set their own. So if the root is red, every child is red too until a child says otherwise.

The list shorthand uses this. Writing ["A", "B", "C"] is the same as {"text":"A", "extra":["B", "C"]}: the first item becomes the root, and the rest become its children. That means [{"text":"A","color":"red"}, "B", "C"] shows all three letters in red, because “B” and “C” are children of the red “A”.

Under the Hood (skippable) The whole format is recursive: a component can contain components, which can contain components, forever. That’s how a single message can mix colors, clickable words, and hover tooltips: each piece is its own little component nested inside the others. You almost never need deep nesting as a beginner, but it’s why the format can do everything from a one-word chat line to a full interactive menu.

Almost every field is optional. A text component doesn’t have to be complicated. {"text":"hi"} is a perfectly good one.

Color and style

The most common thing you’ll add is color and style. These are formatting fields you put right inside the component object, next to text.

The color field takes either one of Minecraft’s 16 named colors or a custom hex code:

  • Named colors: black, dark_blue, dark_green, dark_aqua, dark_red, dark_purple, gold, gray, dark_gray, blue, green, aqua, red, light_purple, yellow, white.
  • A hex color like "#FF8800": a # followed by a 6-digit hexadecimal color, the same kind of code used for colors on the web. This lets you pick any color, not just the 16 named ones.

So {"text":"Danger!","color":"red"} is red, and {"text":"Sunset","color":"#FF8800"} is a custom orange.

On top of color, there are several true/false style fields. Each is a boolean: true turns it on, false turns it off.

  • bold: heavier text.
  • italic: slanted text.
  • underlined: a line under the text.
  • strikethrough: a line through the text.
  • obfuscated: scrambled, constantly-changing characters (the classic “magic” garble).

You can combine them freely. This component is bold, italic, and gold:

{"text":"Legendary Sword","color":"gold","bold":true,"italic":true}

Try It! The italic field has a sneaky use: some text is italic by default (like custom item names you’ll meet in Chapter 22). Setting "italic":false is how you turn that off later. For now, just remember that false is a real choice too, alongside true.

There’s also a font field that points at a font from a resource pack. It defaults to "minecraft:default" (the normal font), and there’s a built-in alternate font you can name as "alt". (Those are the two built-in font names; to see what the alt font actually looks like, try it in-game.) We’ll only use the built-in ones here; making your own fonts is a resource-pack topic for Chapter 30.

Dynamic content: text that fills itself in

So far our components show fixed text we typed. But components can also show dynamic content: values the game fills in when the message is sent. You choose which kind by including a special field instead of (or alongside) text. There are four kinds worth knowing now.

translate — built-in translations

The translate field shows a piece of text in the player’s own language. Minecraft ships with translation keys for nearly everything, and each player sees the message in whatever language their game is set to.

{"translate":"item.minecraft.diamond"}

That shows the word “Diamond,” translated for each player. The key item.minecraft.diamond is the identifier Minecraft uses internally for that item’s name.

Translations can have slots to fill in, written as %s in the translation text. You fill them with the with field, a list of components, one per slot:

{"translate":"%s joined the game","with":[{"text":"Steve","color":"yellow"}]}

If a key doesn’t exist, the game just shows the key text itself; you can supply a fallback field with backup text to show instead.

selector — entity names

The selector field shows the name of whatever a target selector picks (you learned selectors in Chapter 3). The game fills in the actual name(s) when the message is sent.

{"selector":"@p"}

That shows the nearest player’s name. If the selector could match more than one entity, separate names with a comma; if you want to be sure it’s exactly one, add limit=1 to the selector, like "@p[limit=1]".

score — a scoreboard value (preview)

The score field shows a number from the scoreboard, Minecraft’s system for tracking numbers per player. You’ll learn scoreboards properly in Chapter 11; here’s a preview so you recognize it. The score field is itself a small object with a name (whose score to show) and an objective (which counter):

{"score":{"name":"@s","objective":"coins"}}

That would show the running player’s value in a “coins” counter. Don’t worry about making a scoreboard yet; just know that this is how a live number gets into a message.

Under the Hood (skippable) Dynamic values are filled in once, at the moment the message is sent, a process the wiki calls resolution. A score that shows “100” stays “100” in that already-sent message even if the score later changes. Text components don’t keep updating themselves; each send is a fresh snapshot.

nbt — data from a block, entity, or storage (preview)

The nbt field shows raw data values from the game: from an entity, a block, or command storage (a place to keep data, taught in Chapter 12). It uses an NBT path (a way to point at one piece of data) and a source saying where to look:

{"nbt":"SelectedItem.id","entity":"@s","source":"entity"}

That would show the ID of the item the running player is holding. Like score, the data is filled in when the message is sent. We’re previewing this so you recognize it later; the storage side comes in Chapter 12.

Making text interactive: click and hover

Here’s where text components stop being just pretty and start being useful. Two fields make text respond to the player:

  • click_event: what happens when the player clicks the text.
  • hover_event: a tooltip shown when the player hovers the mouse over the text.

There’s also a simpler third field, insertion: when a player shift-clicks the text, the string you put here is inserted into their chat input (it adds to whatever they were typing rather than replacing it). It only works in chat messages. We won’t use it in the walkthrough, but it’s good to know it exists alongside the two big ones.

Modern Minecraft These two fields are written in snake_case: click_event and hover_event, with an underscore. A lot of older tutorials and YouTube videos from before this change write them in camelCase: clickEvent and hoverEvent, no underscore. Those no longer work in current Java Edition. If you copy an old example and the click does nothing, the underscore is the first thing to check. The same goes for the action names below: they’re snake_case too.

click_event

The click_event field is an object. Inside it, an action field names what kind of click behavior you want, and the other fields give it details. These are the available actions:

  • open_url: opens a web link in the player’s browser. Needs a url field.
  • run_command: runs a command as if the player typed it in chat. Needs a command field. The command does not need a leading / slash. (It can only run commands the player has permission for, and not ones that send chat directly.)
  • suggest_command: opens chat and fills in some text or a command, ready for the player to edit and press enter. Needs a command field.
  • copy_to_clipboard: copies text to the player’s clipboard. Needs a value field.
  • change_page: in a written book only, jumps to a page number. Needs a page field.
  • show_dialog: opens a custom pop-up screen (a dialog). Needs a dialog field. Dialogs are a whole feature of their own, covered in Chapter 39; this is just so you know the action exists.
  • custom: sends a custom event to the server (it does nothing on a normal vanilla server; it’s for servers with their own add-ons). Takes an id and an optional payload.
  • open_file: used by the game itself (for example when you take a screenshot). Servers and data packs can’t send this one, so you won’t use it.

A clickable component looks like this:

{"text":"[Click for a diamond]","color":"aqua","click_event":{"action":"run_command","command":"give @s diamond"}}

Clicking that text runs give @s diamond for the player.

hover_event

The hover_event field is also an object with an action field. The actions are:

  • show_text: shows a text component as a tooltip. The text goes in a value field. (Note: a tooltip’s own text can’t itself have working clicks or hovers; tooltips are display-only.)
  • show_item: shows an item’s tooltip, as if hovering it in your inventory. Takes an id (the item), an optional count, and optional components (extra item data, which you’ll meet in Chapter 21).
  • show_entity: shows an entity’s name, type, and UUID. Takes an id (the entity type), an optional name, and a uuid.

A hovering component:

{"text":"Hover me","color":"yellow","hover_event":{"action":"show_text","value":{"text":"Surprise!","color":"green"}}}

And you can put both click_event and hover_event on the same component: clickable and hoverable at once. You’ll do exactly that in the walkthrough.

Sending a component: /tellraw

A text component is just data, so something has to send it. The first sender is /tellraw, which you met in Chapter 1. Its full form is:

/tellraw <targets> <message>

<targets> is a player selector (it must select players, not other entities), and <message> is a text component. So everything you’ve learned in this chapter goes in the <message> slot.

A few real examples, exactly as the game accepts them. Type each into the chat box and press Enter:

/tellraw @a {"text":"I am blue","color":"blue"} /tellraw @a {"text":"Text1\nText2"} /tellraw @p {"translate":"item.minecraft.diamond"}

The second one shows a handy trick: \n inside the text starts a new line.

Big screen text: /title

/tellraw writes to chat. /title writes big text on the screen, the kind you see at the center of the display when something dramatic happens. It has three places it can put text, plus controls for timing:

/title <targets> (title|subtitle|actionbar) <text> /title <targets> times <fadeIn> <stay> <fadeOut> /title <targets> (clear|reset)

  • title: large center-screen text.
  • subtitle: a smaller line just below the title. (A subtitle only appears together with a title, so set the subtitle first, then the title.)
  • actionbar: a line of text just above the hotbar.
  • times <fadeIn> <stay> <fadeOut>: how long, in ticks (1/20 of a second), the title fades in, stays, and fades out. The defaults are 10, 70, and 20 ticks (about half a second in, three and a half staying, one second out).
  • clear removes the current title; reset puts the timing back to defaults.

In Java Edition, the <text> is a full text component, so it gets color and style just like /tellraw. This pair shows a bold title with a gray italic subtitle. Type them one after the other:

/title @a subtitle {"text":"The story begins...","color":"gray","italic":true} /title @a title {"text":"Chapter I","bold":true}

Notice the order: subtitle first, then title, because the subtitle rides along with the title that follows it.

Walkthrough: a styled welcome message

Time to put it together. You’ll greet the player with a big title on screen and a colored, clickable chat line, typing each command into the chat box of your test world.

Step 1 — the title. Type these two, in this order, so the subtitle rides along with the title:

/title @a subtitle {"text":"A grand adventure","color":"gray","italic":true} /title @a title {"text":"Welcome!","color":"gold","bold":true}

A gold “Welcome!” should fade in over a gray italic subtitle.

Step 2 — a colored chat line. Now build a greeting from several child components and send it:

/tellraw @a ["",{"text":"[Welcome] ","color":"aqua","bold":true},{"text":"Hello, "},{"selector":"@p"},{"text":"! Glad you're here."}]

Look at that line closely. It’s a lot of this chapter at once. The list starts with "" (an empty string as the root, so nothing inherits an accidental color), then a bold aqua tag, then plain text, then a selector that fills in the nearest player’s name, then more plain text.

Step 3 — make it clickable. Finally, a single component carrying both a click_event (which runs give @s diamond) and a hover_event (which shows a tooltip):

/tellraw @a {"text":"[Click here for a free diamond]","color":"green","underlined":true,"click_event":{"action":"run_command","command":"give @s diamond"},"hover_event":{"action":"show_text","value":{"text":"Yes, really — click it!","color":"yellow"}}}

You should see the green underlined line appear; hovering it shows the yellow tooltip, and clicking it hands you a diamond.

Figure (to be captured). the gold “Welcome!” title with gray italic subtitle on screen, and the colored clickable chat lines below; mouse hovering the green line shows the yellow tooltip

Modern Minecraft Right now you’re typing each of these lines by hand, which is the fastest way to see what every field does. Later you’ll save a sequence like this so it fires on its own: in a command block inside the world (Chapter 6), or as a function in a data pack (Part III) that can run the whole greeting the moment the pack loads. For now, the chat box is where you experiment.

Practice

  1. Recolor the tag. Change the [Welcome] tag in the chat line to a custom hex color of your choice (for example "#FF55AA"). Type the line again to see it.

  2. Add a help button. Send a /tellraw line that’s a clickable [Help] button. Use suggest_command (not run_command) so that clicking it fills in a command in the player’s chat instead of running it (for example suggesting /time set day). Give it a hover_event with show_text explaining what it does.

  3. An action-bar status. Send a /title @a actionbar {...} line that prints a short status message just above the hotbar in a color of your choice. Notice how the action bar behaves differently from the big center title.

  4. A translated word. Send a /tellraw that uses {"translate":"item.minecraft.diamond"} somewhere in a sentence (inside a list with other text), and confirm it shows the item’s name.

Try It! Combine this with Chapter 4’s /execute: type /execute as @a run tellraw @s {"text":"Hi!"} so the message runs once per player, with each player’s own name available to a selector inside. Think about why running it as each player changes what @s and @p mean.

What Can Go Wrong

  • You wrote clickEvent / hoverEvent and nothing happens. This is the single most common text-component mistake today, because so many older tutorials use the camelCase spelling. Current Java Edition needs the snake_case click_event and hover_event, with an underscore. Same for the action names (run_command, show_text, and so on). Fix the spelling and the click comes back to life.

  • The command reports a red JSON error. Text components are written in the JSON-like format you’ll meet properly in Chapter 8, and the same rules apply: every { needs a matching }, every [ a matching ], strings need their quotes, and there are no trailing commas after the last field. A long /tellraw line is easy to miscount, so type it carefully. The red feedback message points at roughly where the parser got confused.

  • /tellraw says it can’t find players, or refuses your selector. /tellraw and /title only target players. A selector like @e (all entities) or one that resolves to a mob will be rejected, so use @a, @p, @s (when run by a player), or @r. If no players match, the command simply has no one to message.

  • Your subtitle never shows. A subtitle only appears with a title. If you set subtitle but never send a title afterward, there’s nothing for it to ride along with. Set the subtitle first, then the title. The order in the walkthrough does this on purpose.

What You Know Now

You can build a text component: color it (named colors or #hex), style it (bold, italic, underlined, strikethrough, obfuscated), nest pieces as children that inherit formatting, and fill in dynamic content with translate, selector, and (previewed) score and nbt. You can make text interactive with click_event and hover_event, and you know they’re snake_case, unlike the old camelCase tutorials. You can send a component to chat with /tellraw and put big text on screen with /title (title, subtitle, action bar, and times), and you’ve typed out a styled, clickable welcome message of your own. Next chapter you’ll move commands off your keyboard and into the world with command blocks, so a button press can fire a message like this for you. (And later, in Chapter 11, scoreboards will make that score field come alive.)

Chapter 6 — Command Blocks

What You’ll Build

In the last five chapters every command you ran vanished the moment it finished. You typed it, pressed Enter, watched it work, and if you wanted it again you typed it again. At the end of Chapter 1 you felt the itch: “I wish I could run all of these at once.” This chapter scratches the first half of that itch by moving a command off your keyboard and into the world, where a button press, a lever, or a trail of redstone runs it for you.

The tool is the command block: a special block that holds a command and runs it when redstone powers it. By the end of this chapter you’ll hand yourself a command block, type a command into it, and trigger it with a button. Then you’ll line several command blocks up so a single button press fires a whole sequence in order, turning that ten-line “set up my test area” routine from Chapter 1 into one press of one button. Along the way you’ll meet the three kinds of command block and the switches that change how each one behaves. And at the end you’ll bump into the wall that command blocks can’t get past, which is exactly the wall the rest of the book is built to knock down.

Everything here happens in your Creative test world with cheats on, the same world you’ve used since Chapter 1.

Concepts

A command block is an indestructible block that runs a command when it’s activated by redstone. You can’t craft one, mine one, or use one in Survival without cheats, so it isn’t a normal building block. It’s a tool for Creative worlds, servers, and custom maps. Think of it as a command you’ve written down and nailed to a spot in the world, ready to fire whenever it gets a redstone nudge.

There are three kinds, and the game colors them so you can tell them apart at a glance:

  • An impulse command block (orange) runs its command once each time it’s activated. This is the default, and the one you’ll use most.
  • A repeating command block (purple) runs its command every game tick (20 times a second) for as long as it stays activated.
  • A chain command block (cyan) runs its command when the command block pointing into it runs. It’s the link you use to join blocks into a sequence.

Under the Hood (skippable) A command block always runs at permission level 2, the operator level. That’s genuinely useful on a server: you can offer players a button that runs a /give they’d never be allowed to type themselves, without handing them operator powers. You don’t need this yet; it’s just why command blocks are considered an “operator” tool.

Getting a command block

You can’t find a command block in the normal Creative menu unless you switch it on, so the quickest way is the /give command you already know from Chapter 1. Open the chat box and type:

/give @s command_block

A command block drops into your inventory. Place it like any other block. (You can only place and break command blocks while you’re an operator in Creative mode, which in your test world you are.)

To put a command inside it, point at the block and use it (right-click) to open the command block GUI, the little editing screen. Type your command into the top box. One nice difference from the chat box: inside a command block the leading slash is optional, so give @s diamond and /give @s diamond both work. Press Tab to autocomplete as you type, then click Done (or press Enter) to save and close. The bottom box, Previous Output, will show the result the next time the block runs, which is handy for spotting a mistake.

What Went Wrong? “Right-clicking the block does nothing / I can’t open it.” The command block GUI only opens for an operator in Creative mode with cheats on. If you’re in Survival or cheats are off, you’ll just place a block you can’t edit. Make sure you’re in your Creative test world.

Triggering it with redstone

A command block sitting there does nothing on its own. It needs to be activated by redstone power. The simplest power source is a button or a lever stuck to the block, but anything that delivers redstone power works: a lever, a button, a block of redstone, a pressure plate, or redstone dust pointing at it.

Let’s build the kit button you’ve been wanting. Place a command block, open it, and enter:

give @s diamond_sword

Click Done. Now stick a button on the side of the block (place the button against it) and press the button. A diamond sword lands in your inventory, and it will do so every single time you press, without you ever opening the chat box again. You’ve just built your first machine.

Try It! Put a stone button on the front of the block and label the contraption in your head: “sword dispenser.” Press it five times and watch five swords appear. That repeatability is the whole point of a command block: type the command once, run it forever. Figure (to be captured). a command block with a stone button on its front face, and a diamond sword in the player’s hotbar after pressing

The three switches inside a command block

Open the command block GUI again and look at the row of buttons. Three of them change how the block behaves, and they map onto the three types and a couple of options:

  • Block Type cycles Impulse → Chain → Repeat. Impulse runs once per activation; Repeat runs every tick while powered; Chain runs only when triggered by a block aimed into it. The block changes color (orange, purple, cyan) so you can read a build at a glance.
  • Redstone switches between Needs Redstone and Always Active. “Needs Redstone” (the default for impulse and repeat blocks) means the block only runs when something powers it. “Always Active” means it runs without any redstone at all, which is the normal setting for chain blocks.
  • Condition switches between Unconditional and Conditional. An unconditional block (the default) always runs. A conditional block runs its command only if the command block behind it ran successfully, which lets you build “do this only if that worked” logic.

A repeating command block is worth a quick experiment: set one to Repeat, give it effect give @a minecraft:night_vision 2 0 true, and power it with a lever. Flip the lever on and everyone has permanent night vision, refreshed every tick; flip it off and it fades. That “keep doing this while powered” behavior is what Repeat is for.

What Went Wrong? “I pressed the button and nothing happened.” Three things to check. First, the block needs redstone power actually touching it (a button on the block, a lever beside it). Second, an impulse block fires once per activation, so if the button’s already been pressed and reset, press it again. Third, on a server or in a stubborn world, confirm the command_blocks_work game rule is on (it’s on by default).

Chaining: one button, a whole sequence

Now the second half of Chapter 1’s wish: running many commands from a single press. This is what chain command blocks are for. The rule is simple: when a command block runs, it triggers the chain command block it’s pointing at, which runs and triggers the next one, and so on down the line, all in the same tick and in order.

Here’s how to rebuild Chapter 1’s “set up my test area” as one button:

  1. Place an impulse command block. This is the trigger. Give it the first command: say Setting up the test area...
  2. In a straight line, place more command blocks so each one faces into the next (place each new block by aiming at the previous one). Set every block after the first to Chain type and Always Active.
  3. Fill the chain blocks with the rest of the sequence, one command each: give @s diamond_sword, then give @s diamond_pickaxe, then give @s bread 16, then summon cow, then summon pig, then summon zombie, and finally say Test area ready!.
  4. Put a button on the first (impulse) block and press it.

The impulse block fires, triggers the first chain block, which triggers the next, straight down the line. One press, and your kit, your mobs, and both messages all happen at once. That ten-line routine you used to type by hand is now a single button.

Figure (to be captured). a row of command blocks — one orange impulse block with a button, followed by several cyan chain blocks all facing the same direction — with a freshly spawned cow, pig, and zombie nearby

What Went Wrong? “Only the first block ran.” The follower blocks must be set to Chain type and they must physically point into one another. A chain block also needs to be Always Active (or powered) to fire. If one block in the middle is still an orange impulse block, the chain breaks there. Walk the line and check each block’s type and the direction it faces.

The wall command blocks hit

Command blocks are brilliant for a contraption in one spot: a trap, a teleport pad, a kit button. But try to build something big with them and the cracks show. The commands are buried inside blocks scattered around your world, so finding the one with the typo means hunting through your build. They don’t travel: there’s no way to hand a friend “my command blocks” as a download the way you’d share a map or a mod. And a real project can need hundreds of commands, which becomes an unmanageable forest of blocks you can’t search, copy, or keep tidy.

What you really want is to write your commands as files: organized in folders, editable in a text editor, searchable, shareable as a single download, and not physically jammed into the world. Minecraft has exactly that system, and it’s where this book is headed next. It’s called a data pack, and the moment you meet it in Part III, every command you’ve learned to type becomes something you can save, name, and run by the dozen.

What Can Go Wrong

  • The block won’t open or won’t place. Command blocks are operator tools: you need Creative mode with cheats on. In Survival, or with cheats off, you can’t edit or break them.
  • Pressing the trigger does nothing. The block needs redstone power reaching it, and an impulse block fires once per activation (press the button again). On servers, the command_blocks_work game rule must be on (it is by default).
  • A chain stops partway. Every follower block must be set to Chain type, must be Always Active (or powered), and must point into the next block. One impulse block or one wrong-facing block in the middle breaks the rest of the line.
  • I can’t tell why a command failed. Open the block and read the Previous Output box at the bottom; it shows the result of the last run, the same success-or-red-failure idea you learned reading feedback in Chapter 1.

What You Know Now

You can hand yourself a command block with /give @s command_block, type a command into its GUI, and fire it with a button or lever, turning a command you used to retype into a machine you press. You know the three types and their colors: impulse (orange, once), repeating (purple, every tick), and chain (cyan, triggered in sequence), plus the Redstone and Condition switches that tune them. You can chain blocks so one button runs a whole routine in order, which finally grants Chapter 1’s wish to “run all of these at once.” And you’ve seen the ceiling: command blocks live buried in the world, can’t be shared, and don’t scale. Next, in Chapter 7, you’ll start climbing past that ceiling by learning what Minecraft is really doing under the hood, on your way to writing commands as files you can save and share.

Chapter 7 — How Minecraft Really Works

What You’ll Understand

Chapter 6 left you at a wall. You can type commands, aim them, and even bottle them into command blocks, but those blocks stay buried in the world, can’t be handed to a friend as a download, and fall apart once a project needs hundreds of them. The question you walked away with was simple: how do I make the commands I’ve been typing permanent and shareable? This chapter answers it, but not with a button or a block. It answers it with an idea, the one the rest of this book rests on: Minecraft builds its world by reading data files, and a data pack is how you hand it your own.

You won’t build anything in this chapter, and you won’t even open a single file yet. Once this idea clicks, though, everything ahead (recipes, custom items, new enchantments, whole dimensions) stops feeling like magic and starts feeling like filling in forms the game already knows how to read. By the end you’ll be able to explain, in your own words, what people mean when they call Minecraft “data-driven,” what a registry is, why folks say “vanilla is just a data pack,” and why this book teaches Java Edition.

What happens when you launch a world

Click Create New World, wait a moment, and you drop into a fresh landscape. It feels like the game invented that world on the spot, and in a way it did. A world (the game also calls it a level) is a single Minecraft “universe” that holds all the blocks and entities across each of its dimensions: the Overworld, the Nether, and the End. (Entities are mobs, dropped items, and other moving things.) Its terrain is procedurally generated, built automatically by the game from a set of rules rather than drawn by hand.

Once the world exists, the game has to keep it running. Nearly every video game, Minecraft included, is driven by one big repeating loop, and one trip around that loop is called a tick. Minecraft normally runs at a steady 20 ticks per second, one tick every 0.05 seconds, and almost everything that happens (a furnace smelting, a creeper stepping toward you, day turning to night) is scheduled by counting ticks. An in-game day is exactly 24,000 ticks, which works out to 20 real minutes.

So a world is really two things stacked together: a big pile of definitions (what a creeper is, what a furnace does, what an oak tree looks like) and a clock (the tick) that makes those definitions come alive, moment by moment. The interesting question, the one this whole book answers, is where those definitions come from. That’s the next section.

Minecraft is data-driven

Here’s the part most players never find out: Minecraft does not have “creeper-ness” or “the recipe for a torch” baked permanently into a locked black box. A huge amount of what makes the game the game is stored as plain data, separate files describing each feature, that Minecraft reads when it loads. That’s what people mean when they say Minecraft is data-driven: its behavior is driven by data files, not hand-wired into the program.

How much is stored this way? A lot. The game’s own machinery for this is the data pack, and a single data pack can configure advancements, dimensions, enchantments, loot tables (the lists that decide what a chest or a defeated mob drops), recipes, structures, biomes, and more. Each of those is just data describing “here is what this thing is and how it behaves.”

Modern Minecraft If you follow older tutorials online, you may see data packs treated as an exotic add-on for experts. That’s out of date. In current Minecraft, core systems like enchantments are themselves defined as data you can add to or replace: enchantments live in the game’s data right alongside recipes and loot. Thinking of Minecraft as “a program that reads data” isn’t a clever trick anymore; it’s just how the game is built.

The payoff of a data-driven game is huge for you, and it’s the answer to the wall you hit in Chapter 6. If the game reads its content instead of hard-coding it, then anywhere the game reads a file, you can supply a file, and the game will treat yours exactly as seriously as its own. That’s the escape from buried command blocks: instead of nailing a command into a spot in the world, you write it into a file you can name, search, copy, and hand to a friend. You don’t need to crack open Minecraft’s program. You just need to learn the shape of the data it already expects. That’s the entire skill this book teaches.

Registries: the master lists of everything

If Minecraft reads its content from data, it needs an organized way to keep track of it all: a set of master lists. Those master lists are called registries. A registry is a catalog of one kind of game thing, where every entry has a name the game can look up.

Minecraft has a registry for blocks, a registry for items, a registry for entity types (the kinds of mobs and other moving things), a registry for enchantments, a registry for biomes, and many more. They’re even organized neatly: there’s a single root registry, named minecraft:root, and every other registry is registered inside it. It’s a master list of master lists.

Most registries are meant only for the game’s own internal use. But some, called dynamic registries, are open: content can be added to them through data packs. That openness is what this whole book runs on. The features you’ll spend your time with (recipes, loot tables, enchantments, dimensions, biomes, and more) are exactly the ones the game lets you supply through a data pack. A few of them, like enchantments, biomes, and dimensions, are those open dynamic registries; others, like recipes and loot tables, are simply files the game reads straight out of your pack. Either way, the door is open. When you make a recipe later, you aren’t sneaking something past Minecraft. You’re handing it a file it was always willing to read.

Under the Hood (skippable) Why “registry” and not just “list”? Because a registry does more than hold things. It hands each entry a stable name so the rest of the game can refer to it without confusion. When a command, a recipe, or another data pack needs to point at “the diamond item” or “the plains biome,” it uses that registered name. We’ll meet those names, and the rules for writing them, properly in Chapter 8.

“Vanilla is just a data pack”

Now the sentence that surprises people. The plain, unmodified game, what players call vanilla Minecraft, defines its own features using a built-in data pack. The recipes you craft with, the loot in naturally generated chests, the biomes you explore: a big chunk of that is supplied by a data pack that ships inside the game, read the same way any data pack is read.

Sit with that for a second, because it changes how you should think about everything ahead. Minecraft doesn’t have a special privileged way of defining its content and a second-class hobby way of defining yours. There is just one system. The game reads the vanilla data pack to set itself up, and it reads your data packs the same way, through the same doors, into the same registries.

That’s genuinely good news for a beginner. The “real” way Mojang adds a recipe and the way you’ll add a recipe in Chapter 9 are the same way. What you learn here is how Minecraft actually works, and you’ll be using the real thing yourself.

Java Edition vs. Bedrock Edition (and why this book uses Java)

Before we go further, one fork in the road. Minecraft comes in two main versions, and they are not the same software under the hood.

Java Edition is the original version, first released back in 2009, and it runs on Windows, macOS, and Linux. Its name comes from the Java programming language it’s written in. Crucially for us, Java Edition’s code is the more open and modifiable of the two: it has by far the most established community of mods and custom servers, and the code itself isn’t scrambled to hide it, so people have documented it thoroughly.

Bedrock Edition is the multi-platform version, the one on phones, tablets, consoles, and the Windows “Minecraft for Windows” app. It’s built on a different codebase written in the C++ programming language so it can run on all those devices, and it has its own official add-on system for custom content.

Both editions can be customized, but they customize differently: different file formats, different folders, different rules. A book that tried to teach both at once would have to say “but on the other edition…” in every paragraph, and you’d learn neither well. So this book teaches Java Edition data packs. When you read “Minecraft” from here on, picture Java Edition unless we say otherwise.

Try It! Open Minecraft: Java Edition and start creating a new world. On the More tab of the Create New World screen there’s a Data Packs button. Click it. You’ll see the world already lists data packs available to it, with a place to drop your own in later. You don’t need to add anything yet; just notice that the door is right there, built into the menu. Figure (to be captured). Create New World → More tab → Data Packs screen, with the built-in pack listed

What a data pack actually is (plain English)

We’ve used the words “data pack” a lot. Time to pin them down, in plain language, with no file details yet (those come in Chapter 9).

A data pack is a collection of data that configures features of Minecraft. Physically, it’s nothing fancier than a folder (or a .zip file) that contains a special marker file telling the game “this folder is a data pack.” Drop that folder into a world’s data-pack location and the game will read it, layering your definitions on top of the vanilla ones, adding new features or modifying existing ones.

It helps to know what a data pack is not. Minecraft has a sibling system called a resource pack, and the two are easy to mix up. A resource pack changes how the game looks and sounds (textures, models, music, sound effects, fonts) without changing any behavior. A data pack changes what the game does: its rules, its content, its logic. The slogan to remember: resource packs change appearance; data packs change behavior. Many finished creations use both together (a custom sword needs a data pack to exist and a resource pack to look unique), and we’ll cover resource packs later in the book. But data packs are our main event, because data packs are where you change what Minecraft is.

And that’s the whole foundation. Minecraft reads its content from data; that data is organized into registries; the vanilla game is itself a data pack feeding those registries; and a data pack is just a folder of files you add to the very same system. Everything from here is learning the shapes of those files. The next chapter starts with the language they’re written in.

What Can Go Wrong

This chapter has no files to break, but two misunderstandings trip people up before they even start. Clear them up now:

  • “I’ll need to hack or re-program Minecraft.” No. You never touch Minecraft’s program. You add data files to a system that is designed to read added data files. If you ever feel like you’re fighting the game, you’ve probably wandered off the data-pack path. Step back onto it.
  • “My phone/console Minecraft will work the same as this book.” It won’t. That’s Bedrock Edition, which uses a different add-on system. To follow along, you need Minecraft: Java Edition on a Windows, macOS, or Linux computer.

What You Know Now (so far)

You can explain that Minecraft is data-driven (it reads its content from data files), that registries are the master lists that content lives in, that vanilla itself is a built-in data pack, and that a data pack is a folder of files you add to that same system, and you know this book is about Java Edition. Next chapter, you’ll set up a workshop and learn JSON, the simple language every one of those files is written in.

Chapter 8 — Setting Up and Learning the Language

What You’ll Build

In Chapter 7 you learned why Minecraft is the way it is: a data-driven game whose blocks, items, recipes, and worlds are all described in data files the game reads. So far you’ve made the game do things by typing commands; now you start making those things files. In this chapter you set up the tools to write those files, and you learn the two “languages” every file in this book is written in.

By the end you will have a real workshop: a plain-text code editor installed on your computer, ready for the data pack you build in Chapter 9. (You already have the other half of the workshop: the Creative test world with cheats you’ve been using since Chapter 1.) You’ll also be able to read and write JSON (the format every data-pack file uses) and read identifiers, the namespaced names like minecraft:stone that Minecraft uses to label everything. You won’t build a data pack yet (that’s the next chapter), but you’ll be able to hand-write a valid JSON file, spot the small mistakes that break one, and name your own things the way Minecraft expects.


Concepts

Plain-text editor. A plain-text editor is a program that saves exactly the characters you type and nothing else: no fonts, no bold, no hidden formatting. Data-pack files are plain text, so you need a plain-text editor to write them. Word processors like Microsoft Word or Google Docs are the wrong tool: they add invisible formatting and like to “helpfully” change your straight quotes " into curly quotes , which Minecraft cannot read. This is general computer advice, not a Minecraft rule, but it matters: write your files in a code editor, never in a word processor.

Test world. A test world is a throwaway world you use only for trying out data packs: the Creative-with-cheats world you’ve been using since Chapter 1. Cheats are what let you type commands like /reload, and keeping a separate test world means a broken experiment never touches a world you care about. That’s the world we’ll use here.

JSON. JSON (JavaScript Object Notation) is a lightweight, plain-text way of writing structured data as key-value pairs and lists. Minecraft uses JSON to store many things: the pack.mcmeta file that marks a data pack, and the data-pack files that define advancements, loot tables, tags, recipes, and predicates, among others. If you can read and write JSON, you can read and write data packs.

Identifier. An identifier (also called a resource location or namespaced ID) is a name in the form namespace:path that points to one specific game object (a block, an item, a function, and so on) with no ambiguity. minecraft:stone is the identifier for stone. The namespace is the part before the colon; the path is the part after it.

/reload. The /reload command re-reads the data packs in your world so your latest edits take effect without you leaving and rejoining. It will be the button you press all book long: edit a file, save it, run /reload, see the change. (You’ll learn in Chapter 9 exactly what it reloads.)


Walkthrough

Step 1 — Install a code editor

You need one plain-text code editor. This book recommends Visual Studio Code (usually just called VS Code), a free editor from Microsoft. To get it, open your web browser, search for “Visual Studio Code,” download it from the official site (code.visualstudio.com), and run the installer the normal way for your operating system. If you’d rather use something lighter on Windows, Notepad++ is a fine alternative: same idea, smaller program. Either one works for everything in this book; the listings look the same whichever you pick.

These download-and-install steps are ordinary computer steps, not Minecraft features, so they may look a little different depending on your computer and the year. Follow the official site’s instructions if anything has moved.

Try It! In VS Code you can install extensions (small add-ons). Search its Extensions panel for a JSON or Minecraft data-pack extension and install one. A good one will underline broken JSON in red as you type, which catches the mistakes in the “What Can Go Wrong” section before the game ever sees them. Extensions are optional; everything in this book works without them.

Step 2 — Confirm Your Test World Is Ready

You’ve been using a Creative test world with cheats since Chapter 1, and that’s the one we’ll use, so there’s no new world to make. Just load it and check one thing: that /reload is available, because that’s the command you’ll lean on all book long. Open the chat (press T or /), type the reload command, and press Enter:

/reload

Right now you have no data packs of your own, so nothing visible happens, but the command runs without an error, which tells you cheats are on and /reload is available. The /reload command re-reads the current data packs; if a pack has invalid data (such as a broken recipe), the game keeps the previous working version instead of applying the broken one. That safety net is why you can edit boldly: a typo won’t corrupt your world, it just won’t load until you fix it.

Modern Minecraft Some data-pack features can’t be refreshed with /reload alone. A handful of “dynamic” things (Minecraft calls them experimental settings, and they include features like custom dimensions and enchantments) only update when you leave the world and rejoin. Most of what you build early on (functions, recipes, loot tables, tags, advancements, predicates) does reload instantly. You met this distinction in Chapter 7; we’ll point it out again when it matters.

Step 3 — Learn JSON by writing a file

JSON is built from a few simple pieces. Make a scratch folder somewhere easy to find (your Desktop is fine) called json-practice, and create a new file in it. Let’s build that file up one piece at a time, then look at the finished version.

Values. A JSON file holds a single value. A value can be one of these basic types:

  • A string: text wrapped in straight double quotes: "hello", "Hello, world". If you need a double quote inside the text, you escape it with a backslash: "An escaped \" quote".
  • A number, written plainly: 2, -0.5. Numbers can have a decimal point and don’t use quotes.
  • A boolean: exactly true or false (no quotes).
  • An object, a labeled box (below).
  • An array, an ordered list (below).

Objects { }. An object is a collection of key-value pairs wrapped in curly brackets. Each pair is a key (a name, in quotes) and a value, joined by a colon; pairs are separated by commas. Every key in one object must be unique. A value can itself be any type, including another object, which is how JSON nests. Here’s the example straight from how Minecraft documents it:

{
  "Bob": {
    "ID": 1234,
    "lastName": "Ramsay"
  },
  "Alice": {
    "ID": 2345,
    "lastName": "Berg"
  }
}

Read it as: an object with two keys, "Bob" and "Alice". Each of their values is another object with an "ID" (a number) and a "lastName" (a string). Objects inside objects: that’s nesting.

Arrays [ ]. An array is an ordered list of values wrapped in square brackets, separated by commas:

["Bob", "Alice", "Carlos", "Eve"]

The values in a JSON array can even be of different types if you want: a number next to a string next to a boolean is allowed.

Now put it together. Here is a complete, valid practice file using every piece: a string, a number, a boolean, a nested object, and an array:

Desktop/json-practice/my_first.json

{
  "name": "My First File",
  "version": 1,
  "finished": false,
  "author": {
    "id": 1234,
    "tools": ["vscode", "minecraft"]
  }
}

Type that in, then save it. If your editor (or its JSON extension) shows no red underlines, the file is valid JSON. Notice the shape: the whole file is one object { }; "author" is a nested object; and "tools" is an array of two strings. Indentation and line breaks are only there to make it readable (JSON doesn’t require them), but they make a long file far easier to follow, so use them.

Under the Hood (skippable) The five value types you’ll use in data packs are string, number, object, array, and boolean; those are the ones worth learning now. Some real data-pack files also use the empty value null to mean “nothing here,” but it’s rare enough that we won’t lean on it yet; you’ll meet null in a later chapter where a real file actually uses it.

Step 4 — Read identifiers and namespaces

Open your test world’s chat and start typing one of the commands you’ve used since Chapter 1, like /give, and the autocomplete shows names like minecraft:diamond and minecraft:stone. Each of those is an identifier: a namespace:path name. In minecraft:stone, the namespace is minecraft and the path is stone.

Why the namespace exists. A namespace is a labeled grouping that keeps two packs’ names from clashing. Imagine two data packs each add a function called start. Without namespaces those two start functions would collide and break. Give them different namespaces, say minigame_one and minigame_two, and they become minigame_one:start and minigame_two:start, which can’t conflict. This is exactly why your data pack will get its own namespace in Chapter 9.

The minecraft: default. Minecraft reserves the minecraft namespace for the vanilla game. In Java Edition, if you write a name with no colon, the game fills in minecraft for you, so stone and minecraft:stone mean the same thing. Because of that, you should only put your files in the minecraft namespace when you specifically want to add to or change vanilla (for example, adding your function to the built-in minecraft:load group, a Chapter 9 topic). For your own content, use your own namespace.

Naming rules (this is a real Minecraft rule, so follow it exactly). In Java Edition, both the namespace and the path may only contain these characters:

  • lowercase letters az
  • digits 09
  • underscore _
  • hyphen -
  • dot .

The forward slash / is allowed in a path (it makes sub-folders) but not in a namespace. No capital letters, no spaces, no other symbols. The preferred style is snake_case: lowercase words joined by underscores, like my_cool_pack. So mypack:magic_sword is a good identifier; MyPack:Magic Sword is not (capital letters and a space are both illegal).

Pick a good namespace. When you choose your own namespace, make it specific. Don’t use vague “alphabet soup” like nc or an over-broad word like battle_royale; a more descriptive name (your project’s name, or your name plus the project) is easier to find and debug when several packs are loaded at once. In this book the reader’s namespace is always mypack: short, lowercase, and clearly not vanilla.

Modern Minecraft You may see identifiers written without a namespace in old tutorials, like just diamond. That still works in Java Edition because the game assumes minecraft:, but it’s recommended to always write the colon form. Being explicit (minecraft:diamond) makes it obvious which namespace you mean and avoids surprises.


Practice

  1. Write and validate a JSON file. In your json-practice folder, make a new file called pet.json. Inside one object, give it: a "name" string, an "age" number, an "adopted" boolean, and a "favorite_foods" array of two or three strings. Save it. If your editor shows no errors, you wrote valid JSON. (If it does show an error, jump to “What Can Go Wrong.”)

  2. Break it on purpose, then fix it. Add a comma after the last item in your array and save. Watch your editor flag it. Remove the comma to fix it. Getting comfortable making and fixing errors now will pay off in every later chapter.

  3. Spot identifiers in-game. In your test world, press F3 + H together. This turns on “Advanced Tooltips,” so when you hover over any item in your inventory you’ll see its identifier (its namespace:path name) printed under it. Hover over five different items and write down their identifiers. Notice they’re all in the minecraft namespace and all use lowercase snake_case, the same rules you’ll follow for your own names.

Try It! Look closely at the identifiers you collected. Which part is the namespace and which is the path? Can you find an item whose path uses an underscore, like minecraft:iron_ingot? That’s snake_case in the wild.


What Can Go Wrong

A trailing comma. JSON separates items with commas between them, but the last item in an object or array must not be followed by a comma. This is the single most common JSON mistake.

Broken:

{
  "name": "Steve",
  "level": 5,
}

Fixed (the comma after 5 is gone):

{
  "name": "Steve",
  "level": 5
}

A missing or wrong quote. Every key, and every string value, must be wrapped in straight double quotes ". Forgetting one, or letting a word processor turn a straight quote into a curly , breaks the file.

Broken:

{ "name": Steve }

Fixed (the string value is now quoted):

{ "name": "Steve" }

A capital letter or space in a name. Identifiers must be lowercase with no spaces. MyPack:Cool Sword is invalid on two counts: the capital letters and the space. Write it as mypack:cool_sword instead. If a data pack later “can’t find” something you named, check first for a stray capital, space, or typo in the identifier.


What You Know Now

You finished setting up your workshop (a plain-text code editor alongside the cheats-on Creative test world you’ve had since Chapter 1) and learned the two languages the rest of the book is written in. You can hand-write a valid JSON file out of objects, arrays, strings, numbers, and booleans; nest one inside another; and spot the trailing-comma, missing-quote, and bad-name mistakes that break a file. You can read an identifier as a namespace:path name, you know minecraft: is the default namespace, and you know the lowercase snake_case naming rules you’ll use for your own mypack content. You can now build your first real data pack: that’s Chapter 9.

Chapter 9 — Building Your First Data Pack

What You’ll Build

Back in Chapter 6 you hit the wall: command blocks run your commands, but they’re buried in the world, can’t be shared as a download, and don’t scale. The answer is a function: the commands you’ve been typing since Chapter 1, saved in a file the game runs for you, no leading slash required. In Chapter 7 you learned that Minecraft is data-driven: the game reads files to decide what blocks, items, and recipes exist. In Chapter 8 you set up your editor and learned to read JSON and write identifiers like mypack:greeting. Now you put it all together in your Creative test world from Chapter 1.

By the end of this chapter you will have a real, working data pack. It will live inside your test world, it will announce itself in chat every time the world loads, and it will add a brand-new crafting recipe: a chainmail helmet you can craft from nine iron nuggets, which the game has never let you do before. You will turn the pack on, reload it, watch it greet you, and craft your helmet. And because things will break the first few times, you’ll also learn where Minecraft writes down what went wrong, so you can read the message and fix it yourself.

This is the pack you build the rest of the book on top of. Every later chapter adds files to this pack, the one you make right now. We’ll call it mypack from here on.


Concepts

A data pack is a folder (or a .zip file) full of data that configures features of Minecraft: things like advancements, recipes, loot tables, functions, and more. The vanilla game’s own features are defined by a built-in data pack, so when you make one, you’re doing exactly what Minecraft does to itself. (You learned this idea in Chapter 7; now you’ll build one.)

Three new things show up in this chapter:

  • pack.mcmeta: a small text file, written in JSON, whose presence is what tells Minecraft “this folder is a data pack.” Without it, the game ignores your folder entirely. It also holds the pack’s version info and a description.
  • A function: a plain-text file ending in .mcfunction that holds a list of commands, one per line. Running the function runs all those commands in order. This is how you tell the game to do something.
  • A recipe: a JSON file that defines a new way to transform items, like a crafting recipe. This is how you tell the game what a new thing is.

You’ll also meet the minecraft:load function tag, a built-in list the game checks on startup. Any function whose name you add to that list runs automatically when the world loads or reloads. That’s how your greeting will fire on its own.


The Folder Hierarchy

A data pack has a strict shape. Get the folder names right and the game finds everything; get one wrong and the game silently skips it. Here’s the whole tree you’re about to build. Don’t type it yet, just look at the shape:

mypack/
├── pack.mcmeta
└── data/
    ├── mypack/
    │   ├── function/
    │   │   └── load.mcfunction
    │   └── recipe/
    │       └── chainmail_helmet.json
    └── minecraft/
        └── tags/
            └── function/
                └── load.json

Three rules explain that entire picture:

  1. pack.mcmeta sits at the very top, right next to (not inside) the data folder. It is the only mandatory file: it’s what makes the folder a data pack at all.
  2. Everything else lives under data/, sorted into namespace folders. A namespace (from Chapter 8, the part before the colon in namespace:path) keeps your files from colliding with anyone else’s. Your namespace is mypack, so most of your files go under data/mypack/. The special minecraft namespace is for files that hook into or override vanilla, which is why the load tag lives under data/minecraft/.
  3. Inside a namespace, each kind of file gets its own folder named after the registry it belongs to. Functions go in function/. Recipes go in recipe/. The game loads the file data/<namespace>/<registry name>/<path>.json as the thing named <namespace>:<path>. So data/mypack/recipe/chainmail_helmet.json becomes the recipe mypack:chainmail_helmet.

Modern Minecraft Some older tutorials show folder names with an s: functions, recipes, advancements. Current Java Edition uses the singular form: function, recipe, advancement. If you copy an old tutorial and your files don’t load, a stray s on a folder name is a very common reason. The folder list in this book is the current one.

Writing pack.mcmeta

Create a folder named mypack somewhere easy to find, then make a file inside it called exactly pack.mcmeta. Type this into it:

mypack/pack.mcmeta

{
  "pack": {
    "description": "My first data pack",
    "min_format": 88,
    "max_format": 88
  }
}

Here’s what each field means:

  • description is a text component (you met those in Chapter 5). For now, a plain string in quotes is fine. This is the text that appears next to your pack’s name on the Data Packs screen and in the output of the /datapack list command.
  • min_format and max_format describe the range of pack versions your pack is built for. Each is a single number (or, if you ever need to be precise, a [major, minor] pair). The game compares these numbers against its own to decide whether your pack fits. min_format is the lowest version you support, and max_format is the highest.

Why 88? The pack format is a number that changes when Minecraft changes how it reads data-pack files. For Minecraft 1.21.9 and newer, the recommended pack.mcmeta uses min_format: 88 and max_format: 88, so 88 is the known-good current value this book uses throughout.

Don’t memorize a number for any one version, though. The pack format changes over time, and the surest value is the one your copy of Minecraft expects. To read it, run the /version command in-game, or press F3 + V. If the number you get is different from 88, use that number for both min_format and max_format.

Modern Minecraft You will see a LOT of older tutorials use a single field called pack_format instead, like "pack_format": 48. That field still works for backward compatibility, but since the snapshot 25w31a it was replaced by the min_format / max_format pair. There’s also a related legacy field called supported_formats. You only need pack_format or supported_formats if you’re trying to support game versions older than the new scheme, and they must be left out for a pack that only targets new versions. For everything in this book, use min_format / max_format and ignore the old field. (Chapter 46 covers the legacy fields in detail, for the day you need them.)

Under the Hood (skippable) The pack.mcmeta file can hold more than this: sections for experimental features, file filters, and overlays that swap in different files for different game versions. None of that matters yet: a description and a version range is a complete, valid data pack. We come back to the rest in Chapter 46.

Where the pack lives

A data pack belongs inside a world, in that world’s datapacks folder. The full path is your .minecraft/saves/<your world>/datapacks/ folder. Move your whole mypack folder into the datapacks folder of your test world (the Creative, cheats-on world you’ve used since Chapter 1).

To find that folder quickly: open Minecraft, go to Singleplayer, select your test world, click Edit, then Open World Folder. Inside is a folder called datapacks; drop mypack in there. When you’re done it should look like:

<your test world>/
└── datapacks/
    └── mypack/
        ├── pack.mcmeta
        └── data/
            └── ...

Try It! You can also add a data pack while creating a new world: on the Create New World screen, open the More tab and click Data Packs, then drag your pack’s folder into the window. Either way works; the datapacks folder is just where the game keeps them.

At this point you have a valid (if empty) data pack. The game will recognize it. But it doesn’t do anything yet. Let’s fix that.


Your First Function

A function is a text file ending in .mcfunction that contains a list of commands (one command per line) which run top to bottom when the function is called. There are two rules about writing them that trip up everyone at first:

  • No leading slash. When you type a command into the chat bar you start it with /, like /say hi. Inside a .mcfunction file you write the command without the slash, just say hi. The slash is only for the chat bar.
  • Lines starting with # are comments. The game ignores them. Use comments to leave notes to yourself.

Functions live in the function/ folder inside your namespace. Create the file:

mypack/data/mypack/function/load.mcfunction

# This function runs when the pack loads or reloads.
# It announces that mypack is active.
say mypack is loaded! Welcome back.

That’s a complete function. Its name, its identifier, is built from where it sits: namespace mypack, registry folder function, file load → the function mypack:load.

But right now nothing calls it. You could run it by hand by typing /function mypack:load, and it would print your message. Try that if you like. What we actually want, though, is for it to fire automatically every time the world loads.

Wiring it to minecraft:load

Minecraft keeps a built-in list called the minecraft:load function tag. Every function named in that list runs once when the world loads, when the server starts, and every time the data packs are reloaded. (There’s a sibling list, minecraft:tick, that runs functions every single tick; we’ll use that later.)

A tag is itself a small JSON file. Function tags go in the tags/function/ folder, and because we’re adding to Minecraft’s load tag, the file goes under the minecraft namespace, at data/minecraft/tags/function/load.json. Create it:

mypack/data/minecraft/tags/function/load.json

{
  "values": [
    "mypack:load"
  ]
}

The values array is just a list of function names to add to the tag. We’re adding one: mypack:load. (You’ll learn the full tag format, including the replace field and how tags from different packs merge, in Chapter 14. For now, this one-line list is all you need.)

Under the Hood (skippable) Why does adding your function name to the minecraft:load tag run it, instead of overwriting Minecraft’s own load functions? Because tag files merge by default: when several packs each add to #minecraft:load, the game combines all their lists rather than letting the last one win. That’s special to tags; most other files override each other. So your load.json quietly joins the party instead of kicking everyone else out.

What Went Wrong? One important catch: functions in minecraft:load run before any player has joined the world. That’s fine for say, which broadcasts to chat. But it means commands that need to target a player won’t find one at load time. Keep that in mind much later when you write fancier load functions. For now, say works perfectly.

Save both files. We’ll turn the pack on shortly, but first let’s add the recipe so we have something to craft.


Your First Recipe

A recipe is a JSON file that tells the game about a new item transformation. Crafting, smelting, stonecutting, and smithing are all recipes. We’re making the simplest interesting kind: a shaped crafting recipe, where the ingredients have to be placed in a specific pattern on the crafting grid.

Recipe files go in the recipe/ folder of your namespace. Create:

mypack/data/mypack/recipe/chainmail_helmet.json

{
  "type": "minecraft:crafting_shaped",
  "category": "equipment",
  "pattern": [
    "NNN",
    "N N"
  ],
  "key": {
    "N": "minecraft:iron_nugget"
  },
  "result": {
    "id": "minecraft:chainmail_helmet",
    "count": 1
  }
}

Let’s read it field by field. Every one of these is a real recipe field, copied from the recipe format:

  • type says which kind of recipe this is. minecraft:crafting_shaped means “a shaped crafting-table recipe.”
  • category controls which group it appears under in the recipe book. The allowed values are equipment, building, misc, and redstone; a helmet is equipment. (This field is optional: leave it out and it defaults to misc.)
  • pattern is the shape, drawn as rows of text. Each string is one row of the crafting grid. Our pattern has a full top row ("NNN") and a middle row with a gap ("N N"); a space means “leave this slot empty.” That’s the classic helmet shape: across the top and down the sides. Every row string must be the same length, and a recipe can be up to 3×3.
  • key explains what each letter in the pattern means. We used the letter N, and here we say N is minecraft:iron_nugget. You can pick any single character except a space for a key.
  • result is what you get. id is the item it produces (minecraft:chainmail_helmet) and count is how many (one helmet). count is optional and defaults to 1, but writing it is clearer.

The file is named chainmail_helmet.json, so the recipe’s identifier is mypack:chainmail_helmet.

Modern Minecraft Older recipe tutorials often write the result as "result": {"item": "minecraft:chainmail_helmet"}. Current Java Edition uses id, not item, inside result. If your recipe loads but produces nothing, an old-style item key is a likely culprit.

Try It! The key can point at a tag instead of a single item (for example "#minecraft:planks" to accept any kind of wooden plank) or at a list of allowed items. That, plus shapeless, smelting, stonecutting, and smithing recipes, is all coming in Chapter 15. For now, one item per key is plenty.


Loading the Pack

Your folder is complete. Time to turn it on. Open your test world and press T (or /) to open the chat bar. You’ll type these commands into chat, with the leading slash:

First, check that the game can see your pack:

/datapack list

This lists every data pack the game found, split into available and enabled. You should see mypack in the list. (Hover over it in the chat output to see the description you wrote in pack.mcmeta.) If mypack isn’t there at all, the game didn’t recognize your folder; jump to “What Can Go Wrong” below.

Now enable it:

/datapack enable "file/mypack"

The name to enable is the one shown by /datapack list: packs in your datapacks folder show up as file/mypack. As soon as you enable a pack, the game loads it, and your minecraft:load function fires. You should immediately see your greeting in chat: mypack is loaded! Welcome back.

From now on, whenever you change a file in the pack, you don’t need to disable and re-enable it. Just run:

/reload

/reload re-reads the current data packs so your latest edits take effect without leaving the world, and it re-runs every minecraft:load function, so you’ll see your greeting again each time. This is the command you’ll run hundreds of times while building. (One thing to know: /reload re-reads recipes, functions, tags, loot tables, advancements, and predicates live. A few feature types (the kind marked experimental) need you to fully leave and rejoin the world instead. Recipes and functions, happily, reload live.)

Seeing your recipe

Open a crafting table. Click the recipe book (the little book icon to the left of the grid). Your chainmail helmet recipe is now discoverable there. The quickest way to test it: put nine iron nuggets in your inventory, open a crafting table, and lay them out in the helmet shape: top row full, two on the corners of the middle row, middle slot empty. A chainmail helmet appears in the output. You just crafted something vanilla Minecraft never allowed.

Figure (to be captured). crafting table with iron nuggets arranged in the helmet pattern and a chainmail helmet in the result slot; recipe book open showing the new recipe

Under the Hood (skippable) A recipe normally has to be “discovered” before it shows in your book, usually by picking up one of its ingredients. You can ignore discovery while testing: unless the doLimitedCrafting game rule is turned on, you can craft any recipe whether or not you’ve discovered it. So even before the book shows it, laying out the pattern works.


Practice

These extend the exact pack you just built, so keep working in mypack.

  1. Make the greeting your own. Open load.mcfunction and change the say line to a message you like. Add a second say line below it with another message (remember, one command per line, no slash). Run /reload and watch both lines print.

  2. Add a second recipe. Copy chainmail_helmet.json to a new file, chainmail_chestplate.json, in the same recipe/ folder. Change the result id to minecraft:chainmail_chestplate and change the pattern to a chestplate shape (sides down both edges, full bottom rows): ["N N", "NNN", "NNN"]. Run /reload and craft it. (Don’t forget: the new recipe’s identifier comes from its file name, so the file must be named differently from the first.)

  3. Break it on purpose, then read the error. This is the most useful exercise in the chapter, and it builds the habit you’ll lean on for the rest of the book. Delete the comma after the pattern array’s closing ] so the JSON is invalid, run /reload, and watch the game complain. Then put the comma back and reload to confirm it’s fixed. The next section explains exactly what the complaint is telling you.


What Can Go Wrong

When a data pack misbehaves, Minecraft almost always tells you why; you just have to know where it writes it down. The game keeps a running log of what it’s doing, errors included, and that log is the first place to look when something doesn’t work. Exactly where that log lives on your computer, and how to read it, is the very first thing we cover in Chapter 10 (Debugging), so for now just hold on to the idea that the answer is written down somewhere; you’re never only guessing.

Here are the three failures you’ll hit most, and what each one means. (The exact wording of these messages can shift between Minecraft versions, so learn to recognize the kind of problem rather than memorizing the precise text.)

What Went Wrong? — “Expected value” / JSON syntax errors A message about an unexpected character or expected value, usually with a position number, means your JSON is malformed: a missing comma, an extra comma after the last item in a list, a missing quote, or a mismatched bracket. The position points roughly at where the parser got confused (often just after the real mistake). Open the file, find that spot, and check the punctuation. This is the error you triggered in Practice 3. (If your editor highlights JSON, set up in Chapter 8, it usually flags these before you even reload.)

What Went Wrong? — “Unknown resource location” / unknown item This means you named something the game can’t find, most often a typo in an item ID, like minecraft:iron_nuget (missing a g) or minecraft:chainmail_helemt. The game looks up minecraft:iron_nugget in its registry, doesn’t find your misspelling, and reports it. Fix the spelling of the identifier. The same error appears if you reference a tag or other resource that doesn’t exist.

What Went Wrong? — “Could not find function” / wrong folder structure If a function won’t run, or the game says it can’t find mypack:load, the file is almost certainly in the wrong place or has the wrong name. Re-check the path letter by letter: it must be data/mypack/function/load.mcfunction: singular function, the .mcfunction extension spelled exactly, and the namespace folder named mypack. The same applies if your greeting never appears on load: make sure data/minecraft/tags/function/load.json exists, is spelled correctly, and lists "mypack:load" in its values.

A fourth, sneakier failure: the pack doesn’t appear in /datapack list at all. That means the game didn’t accept the folder as a data pack. The usual cause is a missing, misnamed, or invalid pack.mcmeta. Check it’s named exactly pack.mcmeta (no .txt on the end, which some editors add silently), sits at the top of mypack next to data, and contains valid JSON.


What You Know Now

You built a complete, working data pack from nothing. You know that pack.mcmeta is the file that makes a folder a data pack, and that it carries a description plus a min_format/max_format version range (the modern replacement for the old single pack_format). You know the folder shape (pack.mcmeta at the top, everything else under data/<namespace>/<registry>/) and where packs live inside a world’s datapacks folder. You wrote a .mcfunction file (commands, one per line, no slash, # for comments) and wired it into the minecraft:load tag so it announces your pack automatically. You added a shaped crafting recipe with type, pattern, key, and result, and crafted a chainmail helmet from iron nuggets. And you can turn it all on with /datapack enable, refresh it with /reload, and read latest.log to fix the errors that come up.

You can now build a data pack that runs your own commands on load and adds your own recipes to the game. Everything in the rest of this book extends this same mypack. Next, in Chapter 10, you’ll sharpen the debugging skills you just started using so the inevitable mistakes cost you seconds instead of hours.

Chapter 10 — Debugging and Troubleshooting

What You’ll Build

You just built mypack in Chapter 9. Now comes the part nobody warns you about: sooner or later (probably sooner) one of its files won’t load, a recipe won’t show up, or a command will quietly do nothing. You won’t build a new pack in this chapter. You’ll build a habit for debugging the pack you already have. This short chapter hands you the small toolkit that turns “it’s broken and I have no idea why” into “let me look.” You’ll learn where Minecraft writes down what went wrong (the game log), how to read your position and surroundings with the F3 debug screen, how to poke at a block with the debug stick, and how to ask the game what data an entity or block is actually carrying with /data get. You’ll also pick up the single best habit in this whole book: doing your experimenting in a separate test world instead of the survival world you care about. Everything here comes back as a reference in every later chapter’s “What Can Go Wrong” box, so it’s worth the short read now.

This chapter extends the pack you started in Chapter 9 (your mypack pack) and uses the test world you’ve used since Chapter 1.

Reading the game log

When something goes wrong with a data pack, Minecraft usually tells you; you just have to know where it writes the message. The game keeps a running game log: a plain-text record of what it’s doing, including warnings and errors. Two places show it:

  • In chat / on screen. Many data-pack problems show up the moment you run /reload or load the world: a red message in the chat, or a prompt before the world even opens. These are the ones you’ll see most as a beginner, and you don’t have to go hunting for a file to read them.
  • In the log file on your computer. Minecraft writes the same information (and a lot more detail) to a text log file on disk. If you launched from a console window or the official launcher’s log view, the messages scroll past there too.

What Went Wrong? “I ran /reload and a red message flashed by too fast to read.” Open the chat (press T) and scroll up; chat keeps a history. The same text is also in the log file on disk, where it sits still so you can read it carefully.

Where does that file live? The exact name and folder differ by operating system and launcher, so the surest way to find it is the launcher itself: most launchers have a “logs” or “open game directory” button that takes you straight to it, and run-from-a-window setups print the same text to that window. When in doubt, open the log from your launcher rather than hunting for a path by hand.

What the log is good for is matching a symptom to a cause. A few things are worth knowing for certain:

  • When Minecraft reads your function files, every normal line is parsed as a command, and if any line can’t be parsed, the whole function file refuses to load. One typo on line 12 takes down the entire function, so a function “doing nothing” often means it never loaded at all.
  • If a data pack is corrupted or broken (for example it references something that doesn’t exist, like a non-existent entry added to a vanilla tag) Minecraft won’t just limp along. When you try to open the world it shows an error and offers Safe Mode, which disables every data pack except the built-in vanilla one so you can at least get in and fix things.
  • /reload is forgiving: if your latest edit has invalid data (say, a malformed recipe), the change is simply not applied and the game keeps using the previous working version. So a reload that “did nothing” can mean your newest edit was rejected, so check the log.

Modern Minecraft Older tutorials sometimes tell you to dig through crash reports for every problem. In current Java Edition, most data-pack mistakes are far gentler than a crash: a bad function just won’t load, a bad pack triggers the Safe Mode prompt, and a bad /reload edit is quietly ignored while the old version keeps running. You rarely lose your world to a typo. You just have to read the message.

Minecraft’s exact error wording changes from version to version, and the precise text for a JSON syntax error, an unknown resource location, or a missing function isn’t worth memorizing. This book describes the kind of failure each one is so you can recognize it; the wording you see in your own game is the authority, so read the real message off the screen and match it to the kind of problem it describes.

The F3 debug screen

You met the F3 debug screen back in Chapter 2, where you used it to read your coordinates. Press F3 (on a Mac or some laptops, Fn+F3) and the screen fills with small text: a built-in overlay that shows technical information about where you are and what the game is doing. Here’s the rest of it, the part that’s useful when something’s broken. It looks intimidating, but you only need a few lines of it.

The most important line is your position. The debug screen shows your current coordinates (your XYZ) and which way you’re facing (your rotation), in the upper-left of the screen. It also shows your block position: the whole-number coordinates of the block you’re standing in. (Coordinates can be decimals because you stand between whole blocks; the block position is just those numbers rounded down. You worked through coordinates properly in Chapter 2; here you only need “F3 tells me where I am.”)

The debug screen even replaces your crosshair with a tiny set of colored axes so you can see which way is which: +X is red (east), +Y is green (up), and +Z is blue (south). That’s a handy memory aid when you start placing blocks with commands.

Figure (to be captured). the F3 debug screen, with the XYZ coordinate line and the colored +X/+Y/+Z crosshair annotated

The F3 screen lists other useful information too, including details about your surroundings such as the biome you’re in and information about the block you’re looking at, which is exactly what you want when a command “works here but not there.” When you need a coordinate to type into a command, or you want to confirm you’re standing where you think you are, F3 is the fastest answer.

Try It! Press F3, read your XYZ, walk ten blocks in one direction, and watch which number changes and whether it goes up or down. You’ve just learned, by experiment, which axis you walked along, no memorizing required.

The F3 screen shows coordinates, rotation, block position, and the colored +X/+Y/+Z crosshair, and it also lists the biome you’re in and details about the block you’re looking at. The exact labels for those last two move around as the screen changes between versions, so rather than memorizing them, press F3 and read them off your own screen. They’re right there once you know to look.

Under the Hood If you ever want a cleaner screen (for a screenshot or a recording) the gamerule reducedDebugInfo can hide some of this information. You don’t need it now; it’s just good to know the clutter is adjustable. (Skippable.)

The Debug Stick: inspecting block states

Most blocks carry extra settings called block states (also called block properties): little pieces of data that further define how the block looks or behaves. A button knows which way it faces; a door knows whether it’s open; a redstone lamp knows whether it’s lit. Those are block states.

The debug stick is a special item for looking at and changing those states by hand. It looks exactly like an ordinary stick but with an enchantment-style shimmer. You get it the way you get any item with commands, and you can only get it in a world that has commands turned on, which your test world does. Inside a function in your mypack pack you’d write it without the leading slash, like this:

data/mypack/function/give_debug_stick.mcfunction

# Hands the nearest player a debug stick for poking at block states.
# Run this in your TEST world only (Creative + cheats).
give @s minecraft:debug_stick

After a /reload, run it with /function mypack:give_debug_stick, or just type /give @s minecraft:debug_stick straight into chat; either works in a test world. (Quick reminder from Chapter 9: inside a .mcfunction file there’s no leading /; in the chat bar you keep it.)

Here’s how the debug stick works once you’re holding it:

  • Hit a block (left-click) to select which block-state key you want to work with. Hitting a command block, for example, lets you switch between its conditional key and its facing key.
  • Use the block (right-click) to cycle the value of the selected key. With facing selected on that command block, using it steps through down, east, north, south, up, and west.
  • Sneak while hitting or using to go through the keys or values in reverse order.

The debug stick remembers which key you last picked for each kind of block (that memory is stored on the stick itself as a small piece of component data), so it feels natural once you’ve used it a couple of times.

Figure (to be captured). holding the debug stick, with the on-screen message showing the selected block-state key after hitting a block

What Went Wrong? “I’m in Survival and the debug stick does nothing.” That’s expected. The debug stick only works in Creative mode with cheats enabled, exactly the kind of world this book has you test in. In Survival or Adventure it behaves like a plain stick. (One more quirk: using it on an interactive block, like a chest, without sneaking opens the block instead of editing it. Sneak first.)

The debug stick is a debugging tool, not something you ship in a pack, but it’s perfect for answering “wait, what state is this block actually in?” while you’re testing.

/data get: inspecting data at runtime

The F3 screen and the debug stick tell you about blocks in the world. But a lot of what a data pack does lives in NBT data: the structured data attached to entities, block entities (like chests and command blocks), and a general-purpose store called command storage. When something isn’t behaving, you often need to see that hidden data directly. The command for that is /data.

/data has four instructions (get, merge, modify, and remove) but for debugging you only need the read-only one: get. The other three change data; we’ll use those properly when we reach command storage in Chapter 12. For now, get is your magnifying glass: it reads out the NBT of a block, an entity, or a storage and prints it back to you with no risk of changing anything.

The shape is always the same: data get followed by block <position>, entity <target>, or storage <id>, and then an optional path to zoom in on one piece.

Type these straight into the chat bar in your test world:

  • See the item you’re holding: /data get entity @s SelectedItem
  • Check your own saturation level (a number hidden in your player data): /data get entity @s foodSaturationLevel
  • Read the contents of a chest at a known position (use F3 to find the coordinates): /data get block 1 64 1 Items

You can also store a quick inspection inside your pack so it’s one keystroke away while testing:

data/mypack/function/inspect.mcfunction

# Quick inspector: print the item I'm holding, then my saturation.
# Run with /function mypack:inspect after /reload.
data get entity @s SelectedItem
data get entity @s foodSaturationLevel

What Went Wrong?/data get says it got nothing / no tag exists there.” /data get fails if there’s no data at the path you asked for, for example asking for Items on a block that isn’t a container, or a path that’s spelled differently than the real one. Start broad: run data get entity @s with no path to dump everything, then narrow down to the exact name you see in that output.

Under the Hood data get can also report the length of a list or string, handy later. For example, if a storage holds a list of six numbers, data get of that list returns 6. You don’t need this yet; it becomes useful once you’re storing your own data in Chapter 12. (Skippable.)

Common error patterns and their fixes

Most beginner data-pack problems fall into a handful of buckets. Here’s the field guide: match your symptom on the left, check the cause on the right.

  • “My whole function does nothing after /reload.” One line in the file couldn’t be parsed, so Minecraft refused to load the entire function. Check the log for the complaint, and re-read the file for a typo, a wrong block/item name, or a stray / at the start of a line (functions don’t use the slash). Fix the one bad line and reload again.
  • “My new recipe / edit didn’t take effect, but nothing’s red.” /reload silently ignores an edit with invalid data and keeps the previous working version. So the game is running your old file. Re-check your newest change for a JSON mistake (a trailing comma, a missing quote, the wrong brackets, all from Chapter 8) and reload.
  • “The world won’t even open; it’s asking about Safe Mode.” A pack is corrupted or references something that doesn’t exist (a classic is adding a missing entry to a vanilla tag). Let Safe Mode open the world without packs, fix the offending file, then re-enable your pack.
  • “Is my pack even on?” Type /datapack list to see every pack and whether it’s enabled; hover a pack in that output to read the description from its pack.mcmeta. If yours is missing, it’s in the wrong place; if it’s listed but disabled, run /datapack enable <name>. (These are the same loading commands from Chapter 9.)

Don’t worry about memorizing the exact red-text wording for a JSON syntax error, an unknown resource location, or a “can’t find that function” message; it varies by version. What matters is what each failure means. Read the message your own game prints (it is the real source of truth) and use the buckets above to map it to a fix.

What Went Wrong? The fastest debugging habit of all: break one thing on purpose. Delete a comma in a working recipe, run /reload, and read what the game says. Now you’ve seen that exact message while you already know the cause, so when it shows up for real, you’ll recognize it instantly.

Keep a test world, not your survival world

You’ll notice this whole chapter keeps saying “in your test world.” That’s the rule, and it’s worth stating plainly: do your data-pack experimenting in a separate test world (a Creative world with cheats enabled), not in the survival world you care about. You made exactly such a world back in Chapter 1 for this reason.

Why bother with two worlds?

  • Power. The debug stick only works in Creative with cheats, and /give, /data, /reload, and /datapack all need cheats on. Your test world has all of that; a normal survival world usually doesn’t.
  • Safety. Testing means breaking things: flipping block states, summoning mobs, dealing damage, wiping data. A throwaway world means a mistake costs you nothing. Your survival builds and your hard-won gear stay untouched.
  • A clean slate. When you’re hunting a bug, you want as few moving parts as possible. A fresh flat test world lets you reproduce a problem without your survival world’s chaos getting in the way.

When a pack works in your test world and you’re happy with it, then you add it to the world you actually play, and you’ll already know it loads cleanly.

A first look at /test and the GameTest framework

Everything so far is manual debugging: you, looking. Minecraft also has the beginnings of an automated testing system, and it’s worth knowing it exists even though we won’t use it in depth until Part IX.

The system is called the GameTest framework: a way to run small, repeatable tests in the world (each test usually paired with a saved structure that sets up the scene), and the /test command family runs them. The idea is that instead of checking by hand whether your contraption still works, you describe the test once and let the game run it for you, reporting pass or fail.

In a data pack, this framework is backed by two registry folders you’ll meet properly later:

  • test_environment (data/<namespace>/test_environment/): a way to group up GameTests and give them the right preconditions to run. Think of it as the shared setup several tests need.
  • test_instance (data/<namespace>/test_instance/): a test that can be run by the GameTest framework. This is one individual test.

For now, just file those two names away: test_environment is the setup, test_instance is the test. We’ll build real ones in Part IX, where automated testing gets the full treatment.

Under the Hood Both of those folders are marked by the game as experimental settings: having a valid file in one flags the whole pack as using experimental features, and changes to them don’t pick up with a plain /reload the way functions and recipes do. That’s why this is a Part IX topic, not a first-week one. (Skippable.)

The full Java /test subcommand syntax is a topic of its own, and it’s the kind of thing best learned by typing /test in-game and reading the suggestions the command bar offers you. This chapter stays at the “here’s what these are for” level on purpose; Part IX gives /test the full treatment, with real test_environment and test_instance files behind it.

Practice

  1. Read the log on purpose. In your mypack pack, open your load function from Chapter 9 and misspell the /say command (for example type sayy). Run /reload, find the message, then fix it. You’ve now seen what a broken function looks like and confirmed your fix.
  2. Inspect yourself. Give yourself any item, then run /data get entity @s SelectedItem and read what the game prints. Try it with a plain stick and again with an enchanted tool, and notice how the enchanted one carries more data.
  3. Flip a block state. Give yourself the debug stick with your new give_debug_stick.mcfunction, place a button or a command block, and use the stick to change its facing state. Confirm the change with F3’s “looking at this block” info.
  4. Two-world discipline. If you’ve only got one world so far, make a second fresh Creative+cheats world right now and label it clearly as your test world. From here on, that’s where the experiments happen.

What Can Go Wrong

  • Editing in the wrong world. If /give, /data, or the debug stick “don’t work,” you’re almost certainly in a world without cheats. Check that you’re in your Creative test world.
  • Trusting a silent /reload. No red text does not guarantee success: an invalid edit is ignored silently and the old version keeps running. When in doubt, make a visible change (like editing your /say text) so you can confirm the reload actually took.
  • Hunting the file log when the answer is in chat. Most beginner errors print right in the chat on /reload or when the world opens. Scroll the chat up first; only go digging in the on-disk log for the stubborn ones.

What You Know Now

You can now find and read Minecraft’s game log and match its messages to a cause; read your position and surroundings off the F3 debug screen; use the debug stick to inspect and flip a block’s block states in your test world; use /data get to read the hidden NBT data on an entity, block, or storage at runtime; recognize the common breakage patterns (a function that won’t load, a silently-ignored reload, the Safe Mode prompt) and their fixes; explain why a separate test world is the safe place to experiment; and you’ve met the GameTest framework and its test_environment / test_instance folders well enough to know they’re coming back in Part IX. This is your reference chapter — every “What Can Go Wrong” box from here on assumes these tools are in your hands.

Chapter 11 — Scoreboards: Counting and Comparing

What You’ll Build

This chapter starts a new part of the book: storing and tracking data. Up to now your mypack functions have done things (said messages, summoned mobs, given effects), but they’ve had no memory. The moment a function finishes, the game forgets everything it just did. This part fixes that, and it does so with three different tools. The scoreboard you’ll learn here is the first, and it’s the right tool for one specific job: keeping a whole number attached to a player or an entity, changing it over time, showing it on screen, and comparing it. By the end of this chapter you’ll have added a live kill counter to mypack, a number on the right side of your screen that ticks up every time you defeat a mob, and you’ll finally be able to use execute if score, the comparison you were promised back in Chapter 4.

This chapter extends the pack you started in Chapter 9 (mypack). It assumes you’re comfortable calling functions (Chapter 9), with target selectors like @s and @a (Chapter 3), and with the /execute command — its as/at/run pieces and the idea of forking over many entities (Chapter 4). That’s also where you first saw if score named; here you learn it for real.

Modern Minecraft Older data pack tutorials lean on scoreboards as “Minecraft’s variable system” and try to store everything in them. That’s no longer how experienced creators work. In current Java Edition a scoreboard is one of three storage tools, and it’s best at exactly one kind of data: a whole number you might want to see or compare. Complex data (lists, text, nested settings) belongs in command storage (Chapter 12), and simple on/off flags belong in entity tags (Chapter 13). Chapter 13 ends with a decision guide for picking between them. Keep that in the back of your mind as you read: scoreboards are useful, but they are not the answer to everything.

What a scoreboard is

A scoreboard is, in the game’s own words, a gameplay mechanic used through commands to track, set, and list the scores of entities in many different ways. Strip away the formality and it’s simpler than it sounds: a scoreboard is a big table of numbers. Each row pairs a who (a player or entity) with a what (a named counter), and the cell holds a number.

That named counter is called an objective. An objective tracks a score for entities that meet a single criterion, and every score is a 32-bit integer, a whole number that can range from roughly negative two billion to positive two billion (the exact limits are -2,147,483,648 and 2,147,483,647). Two things matter about that: scores are always whole numbers (never 2.5), and they can be negative.

The who in the table is called a score holder. A score holder’s name can either be the player’s username or the entity’s UUID, a UUID being the long unique ID every entity carries (you met UUIDs glancingly before; just think “the entity’s permanent serial number”). So one objective can hold a different number for every player and every mob at once. The objective is the column; each holder is a row.

An objective has two main properties of its own: a name, used internally when you reference it in commands, and a criterion, which decides what the objective tracks. In Java Edition the name must be a single, case-sensitive string of alphanumeric characters (A–Z and 0–9), hyphen -, plus +, dot . and underscore _. In plain terms: pick a short, lowercase, no-spaces name, the same kind of name you’ve been using for functions.

Creating an objective

Everything about scoreboards happens through one command, /scoreboard, which manages and displays scores for your scoreboard objectives. It has two big families of subcommands: scoreboard objectives ... (which create and configure the counters themselves) and scoreboard players ... (which read and change the numbers in them). We’ll start with creating an objective.

The syntax is:

scoreboard objectives add <objective> <criteria> [<displayName>]

<objective> is the internal name you’ll use everywhere else, <criteria> is what it tracks (next section), and the optional <displayName> is a prettier label shown on screen. Two companions you’ll reach for:

scoreboard objectives list
scoreboard objectives remove <objective>

list lists all existing objectives, and remove deletes the named objective from the scoreboard system, wiping its data from every holder and removing it from any display. If you try to add a name that already exists, the command fails; if you remove one that doesn’t, it fails too. Both are harmless mistakes that just print a red message.

Criteria: what an objective tracks

A criterion determines an objective’s behavior and tracks statistical game elements. When a criterion’s source value changes, the change is automatically reflected in the objective’s score. This is the most important idea in the chapter, so read it twice. The criterion is the difference between a number you control and a number the game controls.

The simplest criterion is dummy. A dummy objective tracks nothing on its own: the game never touches it. Its score only changes when a command changes it. That makes dummy the right choice whenever you want to be in charge of the number: a timer, a points total, a counter you increment yourself. (One note: in Bedrock Edition dummy is the only criterion that exists; the rich automatic ones below are Java-only, another reason this book is a Java book.)

The other criteria are automatic: the game updates them for you whenever the matching thing happens in the world. A good worked example is the deathCount criterion: make an objective with it, and a player’s score increments whenever they die. You never write a command to bump it; the game does, every time that player dies. There are many such criteria for health, hunger, experience, statistics, and more; some are compound criteria with dotted names, like minecraft.killed_by:minecraft.zombie, under which a player’s score increments whenever they are killed by a zombie.

Under the Hood (skippable) Why the two kinds? An automatic criterion is wired to a number the game already keeps (your death total, your play time, a statistic) and the objective just mirrors it. A dummy objective is a blank counter with no wiring, waiting for your commands. A useful rule of thumb: if Minecraft already counts the thing, there’s probably an automatic criterion for it; if it’s your idea (a quest stage, a minigame score), use dummy and drive it yourself.

Finding more criteria Java Edition has a long list of automatic criteria (for health, hunger, experience, triggers, statistics, and more) and the full set runs to dozens of alphabetical names. This chapter sticks to the handful you’ll actually reach for: dummy, deathCount, and the compound minecraft.killed_by:… form. When you want to browse the complete alphabetical list, the in-game command auto-complete (type scoreboard objectives add name and press Tab) and the wiki’s Scoreboard page are the best places to see every criterion at once. The kill counter below is built on dummy plus a function that increments it, so it needs no special criterion name at all.

Changing a score: set, add, remove

To read and change the numbers you use the scoreboard players subcommands. The four you’ll use constantly are:

scoreboard players set <targets> <objective> <score>
scoreboard players add <targets> <objective> <score>
scoreboard players remove <targets> <objective> <score>
scoreboard players get <target> <objective>
  • set: sets the targets’ scores in the given objective, overwriting any previous score. Use it to force a number to an exact value.
  • add: increments the targets’ scores in that objective by the given amount. This is how a counter goes up.
  • remove: decrements the targets’ scores in that objective by the given amount. This is how it goes down.
  • get: returns the scoreboard value. Handy for checking a number, and it can feed execute store result … run scoreboard players get … later.

There’s also reset: scoreboard players reset <targets> [<objective>]. Be careful here: reset does not merely set the scores to 0, it removes the targets from the scoreboard system. So reset is forget this holder entirely, while set … 0 is keep them, value zero. They look the same on screen but mean different things; for a counter you almost always want set … 0, not reset.

Here’s a tiny demo function so the four verbs feel concrete. Create:

mypack/data/mypack/function/kills_demo.mcfunction

# kills_demo — show set / add / remove on the "kills" objective
scoreboard players set @s kills 0
scoreboard players add @s kills 3
scoreboard players remove @s kills 1
scoreboard players get @s kills

After /reload and /function mypack:kills_demo, your kills score is 0, then 3, then 2, and the final get prints 2 back to you. (We’ll create the kills objective itself in the walkthrough.)

Try It! There’s a whole set of arithmetic operations for combining two scores (assignment =, addition +=, subtraction -=, multiplication *=, floor-division /=, modulus %=, swap ><, and min/max < / >) run with scoreboard players operation <targets> <objective> <operation> <source> <objective>. You don’t need them for a simple counter, and we’ll use them in the minigame project (Chapter 33). For now, just know the word operation means “do math between two holders’ scores.”

Displaying a score

A number you can’t see isn’t much fun. Scores can be shown in display slots: these can appear in the player list, on the sidebar at the right side of the screen, or below a player’s name tag. Each slot shows one objective at a time. You set a slot with:

scoreboard objectives setdisplay <slot> [<objective>]

Java Edition names three display slots exactly: sidebar (the panel on the right edge of the screen), list (the tab player list), and below_name (under players’ name tags in the world). For example, scoreboard objectives setdisplay sidebar kills puts the kills objective on the sidebar. Leaving the objective off (scoreboard objectives setdisplay sidebar) clears that slot back to empty.

One detail worth knowing: only the sidebar can show non-player entities’ scores; list and below_name are player-only.

No action-bar slot Those three (sidebar, list, and below_name) are the only slots setdisplay accepts. There’s no action-bar display slot. If you want a score in the action bar, that’s a different tool: use the /title … actionbar command from Chapter 5 with a text component, which can embed a score via the score text component. That’s a text feature, not a setdisplay slot, so reach back to Chapter 5 for it rather than looking for a fourth slot here.

Fake players: numbers under made-up names

Here’s a trick that surprises everyone the first time. A score holder’s name can be any arbitrary username you choose, belonging to no real player at all. A made-up name used this way is called a fake player.

Why bother? Because a fake player gives you a place to store a global number, one that belongs to the whole pack rather than to any particular player. Want a single shared “total mobs spawned” count, or a “current game phase,” or a constant like 100 you compare against? Store it on a fake player:

scoreboard players set #total kills 0
scoreboard players add #total kills 1

#total isn’t a real player, so nobody owns it: it’s just a labeled box for a number. The leading # is a deliberate convention: fake players with names starting with a # character never show up in the sidebar. So a # name is a hidden global that stays out of the on-screen list. Creators use the # prefix for behind-the-scenes values they want to keep hidden from players.

Under the Hood (skippable) Player names can’t contain spaces, so there’s a trick worth knowing: a “figure space” (an invisible-looking character, U+2007) can stand in for a space in a fake player’s display. You won’t need that for normal work (short #names are clearer), but if you ever see a fake player whose name looks like it has gaps, that’s what’s happening.

Comparing scores: if score (the Chapter 4 promise, paid off)

Back in Chapter 4 you met execute if block and execute if entity, and I told you a third condition, if score, was coming once you had scoreboards. Now you do. (if|unless) score checks whether a score has a specific relation to another score, or whether it is in a given range. There are two forms.

Comparing two scores. The syntax is:

(if|unless) score <target> <targetObjective> (<|<=|=|>=|>) <source> <sourceObjective> -> [execute]

The middle piece is one of five comparison operators (<, <=, =, >=, >), read exactly like in math. Here’s an example that checks whether two scores are equal:

execute if score @s A = @s B

That reads: “if my score in objective A equals my score in objective B.” You can compare across holders, too: if score @s kills > #total kills asks whether my kills beat the stored total.

Comparing a score to a range. The second form tests one score against a range of numbers:

(if|unless) score <target> <targetObjective> matches <range> -> [execute]

A <range> is written the same way you wrote distance= ranges back in Chapter 3: 10 means exactly ten, 10.. means ten or more, ..10 means ten or less, and 5..10 means anywhere from five to ten. So if score @s kills matches 10.. means “if my kills are ten or more.” This range form is the one you’ll use most for milestones and thresholds.

Just like if entity and if block, the trailing -> [execute] means another subcommand is optional: if score can sit at the end of a chain (just testing), or be followed by run to do something when the test passes. And unless score is simply if score flipped. This example, execute as @a unless score @s test = @s test run say "Score is reset", fires only when a player has no score set (a value compared to itself fails when it doesn’t exist).

Try It! Selectors can filter by score directly, too. There’s a scores selector argument: @e[scores={<name>=<min>..<max>}]. For example, @a[scores={kills=10..}] targets every player whose kills is ten or more. It’s the same range idea as if score … matches, just packed into a selector. Try swapping one for the other once your counter works.

Walkthrough: a kill counter on the sidebar

Now the real project. We want a number on the sidebar that goes up by one every time you kill a mob. We’ll build it from pieces you now know: a dummy objective named kills, a sidebar display, and a tick-driven check that watches for kills and increments the score. We use dummy plus our own increment so that we stay in full control of when the number moves.

Step 1 — create the objective and show it

Make a setup function that creates the objective and puts it on the sidebar. This should run once, when the pack loads. Create:

mypack/data/mypack/function/score_setup.mcfunction

# score_setup — create the kill counter and show it on the sidebar
scoreboard objectives add kills dummy "Mob Kills"
scoreboard objectives setdisplay sidebar kills

The first line makes a dummy objective named kills with the on-screen label "Mob Kills". The second line shows that objective on the sidebar. Now wire this into the load tag so it runs automatically. You already created this file in Chapter 9 for mypack:load. Add the new function to its list (don’t replace what’s there):

mypack/data/minecraft/tags/function/load.json

{
  "values": [
    "mypack:load",
    "mypack:score_setup"
  ]
}

What Went Wrong? If you run score_setup twice (say, after a second /reload), the second scoreboard objectives add kills dummy … line fails with a red message saying the objective already exists, and that’s harmless. The objective from the first run is still fine. A load function trying to re-create an existing objective is a normal, ignorable warning, not a bug in your pack.

Step 2 — count the kills

A dummy objective won’t move on its own. That’s the whole point of dummy: you drive it. So we make a small function that adds one to a player’s kills, and call it whenever a kill should count. Create a function you call to register a kill:

mypack/data/mypack/function/add_kill.mcfunction

# add_kill — register one mob kill for the player who runs it
scoreboard players add @s kills 1

Run /function mypack:add_kill and your sidebar kills number climbs by one each time. To make it feel automatic in a test, pair it with the /execute skills from Chapter 4. For instance, a tick function that adds a kill for every player standing on a gold block reuses the exact as @a at @s if block pattern you already wrote:

mypack/data/mypack/function/kill_on_gold.mcfunction

# kill_on_gold — demo: stand on a gold block to rack up the counter
execute as @a at @s if block ~ ~-1 ~ minecraft:gold_block run scoreboard players add @s kills 1

This is just a stand-in so you can watch the sidebar move without fighting mobs. To make it run every tick, list it in the tick function tag, the every-tick sibling of the load tag you met in Chapter 9. This is the first function you’ve wanted on every tick, so create the file:

mypack/data/minecraft/tags/function/tick.json

{
  "values": [
    "mypack:kill_on_gold"
  ]
}

/reload, stand on a gold block, and watch “Mob Kills” climb on the sidebar.

Figure (to be captured). the sidebar on the right of the screen titled “Mob Kills” showing a rising number while the player stands on a gold block

Step 3 — react at a milestone

Finally, do something when the counter crosses a threshold. This is where if score earns its keep. Create a function that congratulates the player once they hit ten kills:

mypack/data/mypack/function/kill_milestone.mcfunction

# kill_milestone — celebrate at 10 kills
execute as @a if score @s kills matches 10.. run say Ten kills! Nice work.

as @a forks over every player; if score @s kills matches 10.. passes only for players whose kills is ten or more; and run say … fires for each one who qualifies. Call it from chat with /function mypack:kill_milestone, or add it to a tick function if you want it watched constantly (though a constant version would repeat every tick, so a real pack would set a “done” flag, which is exactly the kind of on/off state you’ll learn to store in Chapters 12 and 13).

When scoreboards are the right tool

You’ve now seen what a scoreboard does well, so here’s the honest summary the rest of this part builds on. Reach for a scoreboard when you need:

  • a whole number: scores are integers, nothing else (no text, no lists, no decimals);
  • that you might display: the sidebar, list, and below-name slots are built for exactly this;
  • or compare: if score and the scores= selector argument make numeric thresholds easy;
  • or have the game track for you: automatic criteria like deathCount update themselves.

That’s a real, common set of needs: kill counts, timers, points, lives, levels. But notice what’s not on the list: anything beyond a single whole number. A player’s chosen difficulty, a list of completed quests, a block of configuration: those are the wrong shape for a scoreboard, and trying to cram them in is the classic “scoreboards as variables” mistake from the Modern Minecraft note at the top. The next chapter introduces command storage, which holds exactly that richer data, and Chapter 13 caps the part with a decision guide so you’ll never have to guess which of the three tools to grab.

What Can Go Wrong

Forgetting to create the objective first. Every scoreboard players … command needs an objective that already exists. If you add @s kills 1 before any objectives add kills … has run, the command fails because the objective doesn’t exist. This is why the walkthrough creates kills in a load function: it’s guaranteed to exist before any other function tries to use it.

Confusing reset with set … 0. Remember the warning from earlier: reset removes the holder from the objective entirely, while set … 0 keeps them with a value of zero. If your sidebar suddenly stops listing a player after you “zeroed” them, you probably used reset when you meant set … 0.

Wrong display-slot name. setdisplay takes exactly sidebar, list, or below_name. A typo like side_bar or belowname won’t match, and the score simply won’t appear. If your number isn’t showing up, check the slot name first, and confirm you actually ran the setdisplay line at all (a freshly added objective is invisible until you display it).

What You Know Now

You can use the first of the book’s three storage tools. You know a scoreboard is a table pairing score holders with objectives, that every score is a whole 32-bit integer, and that an objective’s criterion decides whether you drive the number (dummy) or the game does (automatic criteria like deathCount). You can create objectives with scoreboard objectives add, change scores with set / add / remove (and the careful difference between reset and set 0), show a score with scoreboard objectives setdisplay in the sidebar, list, or below_name slot, and stash global numbers on fake players (with a # prefix to hide them). And you’ve finally delivered on the Chapter 4 promise: execute if score, both the two-score comparison (@s A = @s B) and the matches <range> form, to branch a command on a number. Your mypack pack now shows a live “Mob Kills” counter on the sidebar. Next chapter: command storage, for all the data that isn’t a single number.

Chapter 12 — Command Storage: The Modern Default

What You’ll Build

In the last chapter you used scoreboards to track numbers: kill counts, timers, simple whole numbers you can compare and show on the screen. Scoreboards are great at exactly that: one number per player. But the moment you want to remember something that isn’t a single number (a difficulty setting written as a word, a list of active game rules, a little bundle of related values that belong together), a scoreboard starts to feel like the wrong tool.

This chapter introduces the right tool: command storage. Command storage is a named container that can hold any shape of data (numbers, words, lists, and whole nested structures), and it doesn’t need a block or an entity to live in. It’s the modern default for complex state and configuration in data packs. Along the way you’ll meet NBT, the data format Minecraft uses inside storage (and inside entities and blocks), and you’ll finally learn the full /data command. Back in Chapter 10 you only used /data get to look at things; here you’ll learn to write, change, and remove data too.

By the end you’ll have built a small difficulty-settings config system inside the mypack pack you started in Chapter 9: a storage that remembers a chosen difficulty, a multiplier, and a list of rules, with functions to set it up, read it back, and change it.

This chapter extends your mypack pack and uses the test world you’ve used since Chapter 1.

What command storage is

Command storage is a general-purpose, key-value container that commands can read from and write to, without the overhead of attaching the data to a block or an entity. Think of it as a labeled box that floats free in your world: you don’t have to summon a mob or place a chest to have somewhere to keep your data. You just name a box and start putting things in it.

Each storage is identified by a resource location, the same namespace:path naming you met in Chapter 8 for things like minecraft:stone. So a storage has an ID like mypack:config. The namespace part is what keeps two different packs from clashing: if your pack uses mypack:config and someone else’s pack uses theirpack:config, they’re completely separate boxes that never interfere with each other. That’s a big part of why storage is the recommended place for a pack’s data.

Under the Hood (skippable) Behind the scenes, each namespace’s storage is saved into a file called command_storage.dat inside the data folder of your world save, under a folder named for that namespace. You never touch this file by hand (the /data command does it all for you), but it’s nice to know your storage is genuinely saved with the world and survives quitting and reloading.

A storage is only useful if commands can read and write it. Here’s the split, straight from the wiki: the commands that read from storage are data get, data modify, and execute (if|unless) data; the commands that write to storage are data merge, data modify, data remove, and execute store. Storage can also be read by text components: that’s exactly the nbt text source that Chapter 5 previewed and pointed here. In this chapter we’ll focus on the /data family; execute store (writing a command’s result into storage) and execute if data (using storage as a condition) are useful partners you’ll meet properly in Chapter 27.

A gentle introduction to NBT and SNBT

Before you can put data into storage, you need to know what the data looks like. Minecraft stores data in a format called NBT, short for Named Binary Tag. NBT is a tree of tags, where each tag has a type, a name, and a value. That’s the whole idea: a piece of data, a label for it, and what kind of thing it is.

You won’t write NBT directly; it’s a binary format meant for the computer. Instead you write SNBT, which stands for stringified Named Binary Tag: the readable, text version of NBT that Java Edition commands use. SNBT looks a lot like the JSON you learned in Chapter 8, with one big difference we’ll get to. At the top, SNBT is usually a compound — a set of key-value pairs inside curly braces { }, exactly like a JSON object. Here’s an example straight from the wiki:

{
  key1: 123,
  'key2': 'somevalue1',
  "key3": {
    subkey1: 0x1C8,
    "subkey2": "somevalue2"
  }
}

Notice two things. First, the keys don’t have to be in quotes (key1 works bare); in plain JSON they always must be. Second (and this is the difference that trips people up), NBT has several different kinds of numbers, and you often have to say which kind you mean by adding a letter after the number. JSON just has “number”; NBT has bytes, shorts, integers, longs, floats, and doubles.

Here are the suffix letters you’ll actually use, exactly as the wiki gives them. When the game turns NBT into readable SNBT, “a number is always followed by a letter (lowercase for b, s, f, d, and uppercase for L) except integer”:

  • b: a byte (a tiny whole number), e.g. 3b
  • s: a short (a small whole number), e.g. 3s
  • (no letter): an int (a regular whole number), e.g. 3
  • L: a long (a huge whole number), e.g. 3L
  • f: a float (a decimal number), e.g. 3.2f
  • d: a double (a more precise decimal), e.g. 3.2d

One more very useful rule: true and false are stored as 1b and 0b, a byte that’s 1 or 0. So when you want a yes/no flag in storage, you can write true, and Minecraft keeps it as 1b.

Under the Hood (skippable) When you write a value with no letter, the game picks the type for you: “When no letter is used, it assumes double if there’s a decimal point, int if there’s no decimal point.” So 5 becomes an int and 5.0 becomes a double. If you specifically want a float or a short, add the letter. Most of the time, plain ints and quoted strings are all a beginner needs. Reach for the letters only when a particular game property demands a specific type.

Modern Minecraft Old tutorials lean heavily on raw NBT, treating it as the main way to customize items and store state. In current Java Edition, item customization lives in data components (you’ll meet those in Part VI), and your pack’s own data belongs in command storage. You still need to read NBT (it’s how the game describes entities and blocks), but you rarely have to wrestle with the raw binary format. SNBT in commands is as deep as this book goes.

Writing your first value

Let’s put something in a box. The command to write data is /data modify, and to target a storage you write storage <namespace>:<name>. Here is the shape of the command for storage, the same /data modify storage you saw named in the Book Plan:

/data modify storage mypack:config Difficulty set value "normal"

Read it left to right:

  • data modify: we’re changing data.
  • storage mypack:config: in the storage box named mypack:config.
  • Difficulty: at the tag named Difficulty (this part is an NBT path, coming up next).
  • set value "normal": set it to the value "normal".

If the mypack:config storage didn’t exist yet, this creates it. If Difficulty wasn’t there yet, this adds it. You don’t “declare” a storage ahead of time — writing to it brings it into being.

Remember the rule from Chapter 9: inside a .mcfunction file you write commands without the leading /. So in a function this exact line would read data modify storage mypack:config Difficulty set value "normal". When this chapter shows a command to type into chat to test something, it keeps the /.

Reading it back with /data get

To look at what’s in a storage, use /data get, the same /data get you used in Chapter 10 to inspect things, now pointed at a storage:

/data get storage mypack:config

That reads back the entire contents of the mypack:config storage and prints it to you with syntax highlighting. To read just one piece, add an NBT path:

/data get storage mypack:config Difficulty

There’s a neat bonus to data get. When you point it at a list or a string, it doesn’t print the contents; it returns the length. The wiki gives this exact example. Suppose you store a list:

/data merge storage wiki:example {List:[2,5,8,9,6,10]}

Then running this returns 6, the number of items in the list:

/data get storage wiki:example List

And for a string:

/data merge storage wiki:example {String:"Example string with a character count of 43"}

Running this returns 43, the number of characters:

/data get storage wiki:example String

That length-returning behavior is genuinely handy: it’s how you ask “how many things are in this list?” without writing any extra machinery.

NBT paths: pointing inside the data

That Difficulty in the middle of the command is an NBT path: “a descriptive string used to specify one or more particular elements from an NBT data tree.” When your storage holds a flat value the path is just a name. But storage can hold structures inside structures, and a path is how you dive into them. A path is written as nodes separated by dots (.). Here are the node types you need, taken straight from the wiki’s own examples:

  • foo: the tag named “foo” under the root.
  • foo.bar: the tag “bar” inside the compound “foo”.
  • foo.bar[0]: the first element of the list “bar”. (Lists count from 0, just like the list positions you saw with selectors in Chapter 3.)
  • foo.bar[-1]: the last element of the list “bar”.
  • foo.bar[{baz:5b}]: every element of the list “bar” whose “baz” tag equals 5b.
  • foo{bar:"baz"}: the “foo” tag, but only if its child “bar” has the value "baz".

So a path like Settings.rules[0] means “inside the Settings compound, the first item of the rules list.” You build paths by chaining nodes until you’ve pointed at exactly the data you want.

Under the Hood (skippable) A path doesn’t have to select just one tag — it can select several at once. The wiki notes that commands like data get “require the size of the tag collection obtained by the NBT path to be 1” (you can only get one thing), while data modify “allows the size of the tag collection obtained by the NBT path to be greater than 1” (you can change many matching tags in one command). As a beginner you’ll almost always point at a single tag, so you can set this aside until you need it.

Storing strings, lists, and nested objects

Storage holds whole structures, and that’s the whole point. You can set a tag to an entire structure in one go. Watch what set value can take:

A string (text in quotes):

/data modify storage mypack:config Difficulty set value "hard"

A list (square brackets, items separated by commas):

/data modify storage mypack:config Rules set value ["keep_inventory", "no_mob_griefing"]

A nested object (a compound inside a compound):

/data modify storage mypack:config Settings set value {difficulty: "normal", mob_health_multiplier: 1.5f, hardcore: false}

That last one stores three different kinds of value together (a string, a float, and a yes/no flag, with false kept by the game as 0b) all under one tidy Settings tag. This is what a scoreboard cannot do, and why storage is the default for anything more complicated than a single number. The structure is yours to design: whatever shape your pack’s data wants, you can write it.

The /data command in depth

You’ve now met all four of the /data instructions in passing. Let’s lay them out properly. The wiki lists exactly four: get, merge, modify, and remove. Each one can target a block <targetPos>, an entity <target>, or a storage <target>: the same three kinds of place. Here’s each instruction.

get — read data

/data get storage mypack:config Settings.difficulty

get reads “the entire NBT data or the subsection of the NBT data” at the target and prints it. It can take an optional scale at the end: a number it multiplies the result by, useful when you need a number in different units. With no path, it reads everything; with a path, just that part.

merge — blend in a compound

/data merge storage mypack:config {LastWinner: "Alex", Round: 3}

merge takes a compound of SNBT and blends it into what’s already there. Tags you mention get set; tags you don’t mention are left alone. So the command above adds (or updates) LastWinner and Round without disturbing your Settings tag. merge is the quick way to update a few top-level fields at once.

modify — the precise editor

modify is the workhorse, and it has five operations. These names come straight from the wiki; do not improvise others. After data modify <target> <path> you pick one of:

  • set: “Set the tag specified by <targetPath> to the source data or direct value data.” Replaces whatever was there.
  • merge: “Merge the source data or direct value data into the pointed-to object.” Like the top-level merge above, but aimed at a specific compound via its path.
  • append: “Append the source data or direct value data onto the end of the pointed-to list or array.”
  • prepend: “Prepend the source data or direct value data onto the beginning of the pointed-to list or array.”
  • insert <index>: “Insert the source data or direct value data into the pointed-to list or array as element <index>, then shift higher elements one position upward.” (insert needs a position number; append and prepend don’t.)

So to add a rule to the end of a Rules list:

/data modify storage mypack:config Rules append value "no_fall_damage"

To slip one in at the front:

/data modify storage mypack:config Rules prepend value "no_fall_damage"

To put one specifically at position 1 (the second slot, counting from 0):

/data modify storage mypack:config Rules insert 1 value "no_fall_damage"

remove — delete data

/data remove storage mypack:config Settings.hardcore

remove “removes NBT data at <path>” from the target. Point it at a tag and that tag is gone. There’s one safety rule worth knowing: you can’t remove (or edit) a player’s data this way. “Player NBT data cannot be removed.” Your own storage, though, is entirely yours to prune.

value, from, and string: three ways to feed modify

In every modify example above, you typed the data in directly after the word value. That’s one of three sources the wiki gives for modify (and they apply to set, merge, append, prepend, and insert alike):

  • value <value>: the direct value you type in, as you’ve been doing.
  • from <source>: copy the data from another place, such as another storage, an entity, or a block.
  • string <source>: copy data from a source as a string, with optional start/end character positions to grab just part of it.

The from source is what makes storage feel connected to the rest of the world: you can copy a value out of an entity or a block and into your storage, or shuffle data between two storages. For example, to copy a value from one storage into another:

/data modify storage mypack:config LastResult set from storage mypack:scratch Result

You’ll use from heavily once you start reading live data out of mobs and blocks, which is exactly the next section.

Reading data from entities and blocks

Storage is a box you fill, but /data get can also read the data the game keeps on entities and block entities (blocks that hold extra data, like chests and signs). This is how you inspect the live world, and it’s the same /data get entity you first used, read-only, in Chapter 10.

Here are real examples from the wiki. To view the data of the item you’re holding:

/data get entity @s SelectedItem

To read your current saturation level:

/data get entity @s foodSaturationLevel

These follow the same pattern as storage: data get, then the target (entity @s), then an NBT path (SelectedItem). The path syntax is identical whether you’re reading a storage, an entity, or a block. That’s why you learned paths once and can use them everywhere.

Reading a block works the same way, with block and a position. The wiki walks through a great example: a player has put a written book in a chest and wants the text on one of its pages. They build the path up one piece at a time by running /data get and reading the output, until they reach:

/data get block ~ ~ ~ Items[1].components.minecraft:written_book_content.pages[3].raw

Don’t worry about every part of that path yet (the components piece belongs to Part VI). The point is the technique. You aim /data get at a block, read what comes back, then add one more node to your path to drill deeper, repeating until you’ve isolated exactly the value you want. That trial-and-error path-building is one of the most useful debugging habits in the whole book.

Try It! Stand on a chest with an item in it and run /data get block ~ ~ ~ in chat to dump its entire block data, then add Items to the path and run it again. Watch the output shrink to just the contents. Add [0] to see the first item. You’re learning to read NBT by exploring it, the same skill that lets you copy real game data into your storage with from.

When storage is the right choice

You now have two data tools (scoreboards from Chapter 11 and command storage from this chapter), and a fair question is which one when. The full decision guide (which adds entity tags as a third option) comes in the very next chapter. For now, here’s the storage half of it.

Reach for command storage when:

  • The data is a word, a list, or a bundle of related values rather than a single whole number.
  • The data describes the pack or the world rather than one specific player (configuration, game state, saved settings).
  • You want a clean, namespaced place to keep your pack’s data that won’t clash with other packs.

Reach for a scoreboard when the thing you’re tracking really is a single integer you might compare or display: a kill count, a timer, a level. Storage can hold a number too, but a number you constantly compare and show on the sidebar is a scoreboard’s home turf.

A short way to remember it: scoreboards count; storage remembers. If you’d describe the data as “a number,” try a scoreboard. If you’d describe it as “some information,” reach for storage.

Walkthrough: a difficulty-settings config system

Let’s build something real in mypack: a config storage that remembers a difficulty setting, a matching mob-health multiplier, and a list of active rules, the kind of thing every bigger data pack needs. You’ll write three functions: one to set up the defaults, one to read the config back, and one to change the difficulty.

Step 1 — the init function

This function writes the default config into mypack:config. We store one tidy compound called Settings so all the config lives together.

mypack/data/mypack/function/config_init.mcfunction

# Set up the default difficulty config in storage mypack:config.
# One compound, "Settings", holds everything: a difficulty word,
# a mob-health multiplier (a float), and a list of active rules.
data modify storage mypack:config Settings set value {difficulty: "normal", mob_health_multiplier: 1.0f, rules: ["keep_inventory"]}

One command, and the whole config exists. difficulty is a string, mob_health_multiplier is a float (the f makes sure it’s stored as a decimal), and rules is a list with one rule in it to start.

Step 2 — run it on load

Your pack already runs mypack:load every time it loads, wired up through the minecraft:load function tag back in Chapter 9. Add a line to your existing load function so the config is always set up when the pack loads. Open load.mcfunction and add the call:

mypack/data/mypack/function/load.mcfunction

# The greeting from Chapter 9 stays as it was; we add a call to set up config.
say mypack is loaded! Welcome back.
function mypack:config_init

Now every /reload (or world load) refreshes the config to its defaults. (Later, you’ll likely want to set defaults only if the config doesn’t already exist — that needs execute if data, which is a Chapter 27 topic. For now, resetting on load is perfectly fine and easy to reason about.)

Step 3 — the read function

A small function to dump the whole config so you can see it while testing:

mypack/data/mypack/function/config_get.mcfunction

# Print the whole config, then just the chosen difficulty.
data get storage mypack:config
data get storage mypack:config Settings.difficulty

The first line shows everything; the second drills in with the path Settings.difficulty to show just the chosen difficulty word.

Step 4 — the set-difficulty function

This function switches the config to “hard”: it changes the difficulty word, bumps the multiplier, and adds a rule to the rules list.

mypack/data/mypack/function/set_difficulty.mcfunction

# Switch the config to "hard".
# 1. change the difficulty word with set
data modify storage mypack:config Settings.difficulty set value "hard"
# 2. raise the mob-health multiplier with set
data modify storage mypack:config Settings.mob_health_multiplier set value 2.0f
# 3. add a rule to the END of the rules list with append
data modify storage mypack:config Settings.rules append value "no_mob_griefing"

Three operations, each pointing at a different part of the same Settings compound: two sets and one append. Notice you never had to recreate the whole config — you reached straight in and changed just the pieces you wanted, using NBT paths.

Step 5 — test it

Reload and try the functions in chat (these are typed into chat, so they keep the /):

/reload
/function mypack:config_get
/function mypack:set_difficulty
/function mypack:config_get

The first config_get shows normal with multiplier 1.0f and one rule. After set_difficulty, the second config_get shows hard, multiplier 2.0f, and no_mob_griefing added to the list. You’ve built a working, readable, changeable config system, and there isn’t a scoreboard in sight, because none of this is a single comparable integer.

Figure (to be captured). chat output of /function mypack:config_get before and after set_difficulty, showing the Settings compound change from normal/1.0f to hard/2.0f with the extra rule

Practice

  1. Add a “peaceful” setter. Write mypack:set_peaceful that sets Settings.difficulty to "peaceful", sets Settings.mob_health_multiplier to 0.5f, and uses prepend to add "no_hostile_spawns" to the front of Settings.rules. Compare how prepend differs from the append you used in the walkthrough.

  2. Remove a rule. Write a function that uses data remove to delete the first rule with the path Settings.rules[0], then run config_get to confirm the list got shorter.

  3. Count the rules. Recall that data get on a list returns its length. Write a one-line function that runs data get storage mypack:config Settings.rules and read the number it returns in chat.

  4. Insert in the middle. Use data modify storage mypack:config Settings.rules insert 1 value "no_fire_spread" to slip a new rule into position 1 of the rules list, then read the list back and confirm the new rule landed in the second slot (positions count from 0).

  5. Copy from a block. Place a chest, put an item in it, stand on it, and use data modify storage mypack:config FirstSlot set from block ~ ~ ~ Items[0] to copy the first item into your config storage. Run config_get to see real game data sitting in your box.

What Can Go Wrong

What Went Wrong?“No tag exists at the path” If you read or remove a path that isn’t there, the command fails with a message about no tag existing at that path. Usually it’s a typo in the path or a missing piece, for example asking for Settings.dificulty (misspelled) or Difficulty when you actually stored it under Settings.difficulty. Run data get storage mypack:config with no path to dump everything, then read the real tag names back and fix your path. NBT tag names are case-sensitive, so Settings and settings are two different boxes.

What Went Wrong?appending to something that isn’t a list append, prepend, and insert only work on a list or array. If you point one at a single value or a compound by mistake, the command fails (the wiki’s wording: the target “isn’t a list or array tag”). Make sure the path you’re appending to really points at a [ ] list, for example Settings.rules, not Settings.difficulty.

What Went Wrong?the number is the wrong “kind” NBT cares about number types. If a value needs a float and you write a plain 2 (an int), it may not behave as expected, because 2 and 2.0f are different types of tag. When a value should be a decimal, add the f (float) or d (double) suffix, like 2.0f. When you read data back with data get, the game shows you the letters — that’s your clue to what type each value is.

What You Know Now

You can create a command storage, a namespaced, free-floating container (mypack:config) that needs no block or entity, and fill it with any shape of data. You met NBT and its readable form SNBT, including the type letters (b, s, L, f, d) and the rule that true/false are stored as 1b/0b. You can write data with /data modify storage (set value), read it with /data get (which returns the length of a list or string), and point inside structures with an NBT path (foo.bar[0], [-1], dotted nodes). You learned the full /data command: get, merge, modify (with its five operations set, merge, append, prepend, insert), and remove, plus the three modify sources value, from, and string. You can read live data out of entities and blocks with /data get, and you can say when storage beats a scoreboard. And your mypack pack now has a real, working difficulty-config system.

You can now build: a saved configuration for any data pack; a place to remember lists and structured state across reloads; and the habit of reading game data with /data get to debug. Next chapter adds the third data tool, entity tags, and completes the “which tool for which job?” decision guide.

Chapter 13 — Entity Tags and Choosing the Right Tool

What You’ll Build

Over the last two chapters you met two ways to remember things: scoreboards (Chapter 11), which track numbers, and command storage (Chapter 12), which holds any shape of data you like. This chapter adds the third and simplest tool in the set, the entity tag, and then steps back to answer the question all three chapters have been building toward: given a job, which tool should I reach for?

By the end you’ll be able to slap a plain on/off label onto any entity with /tag, pick out every entity wearing that label with @e[tag=...] (the tag= filter Chapter 3 promised you’d learn here), and tell at a glance whether a task wants a scoreboard, command storage, or an entity tag. To prove it, you’ll add three tiny systems to your mypack pack: a team marker built from tags, a countdown timer built from a scoreboard, and a quest tracker built from command storage, one system per tool, so the differences are impossible to miss.

This chapter extends the pack you started in Chapter 9 (your mypack pack) and uses the test world you’ve used since Chapter 1. It assumes the selector skills from Chapter 3, the scoreboard skills from Chapter 11, and the command-storage skills from Chapter 12.

What an entity tag is

An entity tag is the simplest tracker in the whole game: a short word you stick onto a specific entity that is either there or not there. There’s no number and no structure, just a label that’s present or absent, like a sticker on a box. The game stores these as scoreboard tags: a simple list of single-word strings stored directly in the entity, with a maximum of 1024 tags per entity. Each entity carries its own little list of stickers, and you can ask “does this entity have the red_team sticker?” and get back a plain yes or no.

That’s the whole idea. A scoreboard answers how many? Command storage answers what’s the data? An entity tag answers a simpler question: is this flag on or off for this particular entity? A flag is just programmer-speak for an on/off marker: the entity either has the tag or it doesn’t.

Because a tag lives on the entity itself, it travels with that entity. Tag a zombie boss and that one zombie stays boss until something removes the tag or the zombie dies, and you don’t have to keep a separate list of which zombie is the boss. That’s what makes tags so handy for marking this mob, that armor stand, or these few players out of a crowd.

Under the Hood The game’s own name for entity tags is “scoreboard tags,” and they really are stored alongside the scoreboard system, but don’t let the name fool you. They have nothing to do with the numbers a scoreboard tracks; they’re a separate on/off list. The book calls them entity tags because that describes what they are: labels attached to entities. (Skippable.)

Adding and removing tags with /tag

You manage entity tags with the /tag command. It has exactly three jobs: add a label, remove a label, and list the labels an entity currently has. Here is the syntax:

tag <targets> add <name>
tag <targets> remove <name>
tag <targets> list
  • tag <targets> add <name>: adds the tag <name> to every entity matched by <targets>.
  • tag <targets> remove <name>: removes the tag <name> from every matched entity.
  • tag <targets> list: lists all tags currently on the matched entities.

The <targets> part is a target selector (the same selectors you learned in Chapter 3), so you can tag one entity, a filtered group, or yourself. The <name> is the label: a single word, and (like scoreboard objective names) case-sensitive, so Boss and boss are two different tags.

Let’s give your pack its first tagging function. Inside a .mcfunction file there’s no leading /, just as you’ve done since Chapter 1. This one marks every nearby zombie as belonging to the red team:

data/mypack/function/mark_red_team.mcfunction

# Mark every zombie within 10 blocks as the red team
tag @e[type=minecraft:zombie,distance=..10] add red_team
say Red team assembled!

Run it from chat the way you’ve run your other functions (/function mypack:mark_red_team) after summoning a few zombies near yourself. Every zombie in range now carries the red_team sticker. Nothing looks different yet; the tag is invisible. To prove it’s there, point /tag ... list at one zombie, or (better) use the tag in a selector, which is the next section.

If you ever want to clear the label, the mirror-image function removes it from the same group:

data/mypack/function/clear_red_team.mcfunction

# Take the red team tag back off every nearby zombie
tag @e[type=minecraft:zombie,distance=..10] remove red_team
say Red team disbanded.

What Went Wrong? Adding a tag an entity already has, or removing one it doesn’t have, isn’t an error you need to worry about: the command simply reports that nothing changed for that entity. Tags are safe to “add again”: an entity can hold a given tag only once, so re-adding red_team to an already-red zombie just leaves it red.

Using tags in selectors: @e[tag=...]

Back in Chapter 3 you saw the tag= filter listed but were told its full story would wait for this chapter. Here it is. You filter a selector by tag with the tag= argument, which has four forms:

[tag=<string>]    Include only targets with the specified tag.
[tag=!<string>]   Exclude any targets with the specified tag.
[tag=]            Include only targets with exactly zero tags.
[tag=!]           Include only targets that have at least one tag.

So @e[tag=red_team] means “every entity carrying the red_team sticker,” and @e[tag=!red_team] means “every entity not carrying it.” The two empty forms are occasionally handy: @e[tag=] finds entities with no tags at all, and @e[tag=!] finds entities that have at least one tag of any kind.

You can stack tag filters. Multiple tag arguments are allowed, and all arguments must be fulfilled for an entity to be selected: they’re combined with AND, exactly like the other filters from Chapter 3. So @e[tag=red_team,tag=boss] matches only entities that have both the red_team and the boss stickers, while @e[tag=red_team,tag=!stunned] matches red-team entities that are not stunned.

Now that you can select tagged entities, you can do something visible with the team you marked. Add a function that makes every red-team zombie glow:

data/mypack/function/red_team_glow.mcfunction

# Make every red team member glow for 30 seconds
effect give @e[tag=red_team] minecraft:glowing 30 0

Run mypack:mark_red_team to assign the team, then mypack:red_team_glow, and the marked zombies light up with the glowing outline while any un-marked mobs nearby stay dark. That outline is your proof the tag is doing its job.

Figure (to be captured). a cluster of glowing red-team zombies with one ordinary, non-glowing zombie standing just outside the original 10-block range

Try It! Combine tags with everything else from Chapter 3. @e[type=minecraft:zombie,tag=red_team,distance=..20] targets red-team zombies within twenty blocks. Or split your mobs into two teams: tag one group red_team and another blue_team, then write a function that only buffs @e[tag=red_team] and another that only buffs @e[tag=blue_team]. Tags turn one undifferentiated crowd into named groups you can command separately.

Two very different things both called “tag”

Here is the single most important thing to keep straight in this chapter, because the next chapter is going to reuse the word “tag” for something completely different.

Modern Minecraft The word tag means two unrelated things in Minecraft, and tutorials online rarely warn you:

  1. Entity tag (this chapter): a runtime label you put on one specific entity with the /tag command, and check with @e[tag=...]. It’s a sticker on a single mob/player/armor stand, added and removed live while the world runs.
  2. Registry tag (Chapter 14): a JSON file in your data pack that groups types of things, written with a leading #, like #minecraft:logs standing for “every kind of log block.” It groups whole categories of blocks, items, or entity types, and you can’t change it with a command while playing.

The game itself draws the line: the /tag command is distinct from entity type tags, which are applied to entity types and can’t be changed by commands. When Chapter 14 starts talking about #minecraft:logs, remember it means the second kind, a group of block types in a file, not the on/off sticker you learned here.

Quick mental test: red_team on a particular zombie is an entity tag (one entity, on/off, runtime). #minecraft:logs standing for all log blocks is a registry tag (a category of types, defined in a file). Same word, two worlds.

When an entity tag is the right choice

Reach for an entity tag when the thing you want to remember is a simple yes/no flag that belongs to a specific entity. Signs you want a tag:

  • You’re marking entities so a later command can find them again: “the mobs in this arena,” “the armor stand that anchors my machine,” “players who already opened the chest.”
  • The answer is genuinely on-or-off. There’s no count to keep and no structured data, just is it marked or not?
  • The flag should ride along with the entity. Tag a mob and the mark stays with that mob wherever it wanders, with no separate bookkeeping.

If you catch yourself wanting to store a number on the entity, or a list, or several related values, a tag is the wrong tool. That’s a job for the next two tools, which is exactly what the decision guide is about.

The decision guide: which tool for which job?

You now own all three of Part IV’s tools. Here’s how to choose between them. Ask yourself what shape the information is:

If you need to track……reach forbecause
Complex or structured data (a list, nested values, configuration, anything that isn’t one plain number)Command storage (Ch 12)storage is a general-purpose, namespaced key-value container that holds any NBT shape, with no entity required
An integer to count and maybe display (a kill count, a score, a countdown on the sidebar)Scoreboard (Ch 11)objectives are made for whole numbers you can add to, compare, and show on screen
A simple on/off flag on a specific entity (“is this mob marked?”, “did this player do the thing yet?”)Entity tag (this chapter)a tag is the lightest possible marker: present or absent, riding along on the entity itself

A few rules of thumb the table doesn’t spell out:

  • Start with the shape of the data, not the system you know best. Old tutorials lean on scoreboards for everything, even things that aren’t numbers, because scoreboards used to be the only flexible option. In current Java Edition that’s the wrong instinct: if the data isn’t a plain integer, command storage is almost always cleaner.
  • A flag is not a number. “Has this player finished the quest?” is on/off, so that’s a tag, not a scoreboard objective set to 0 or 1.
  • It’s fine to mix them. A real system often uses all three: tags to mark which entities are in play, a scoreboard for the score, and storage for the settings. The three practice systems below each use just one tool so you can feel the difference, but nothing stops you combining them.

Modern Minecraft If you’ve followed older data-pack guides, you’ve probably seen scoreboards used as a catch-all “variable system,” with fake players standing in for strings and flags. That style still works, but it’s no longer how modern packs are built. The split this book teaches (numbers on scoreboards, structured data in command storage, on/off flags as entity tags) matches how current Java Edition data packs are actually written, and it’ll keep your packs far easier to read.

Practice: three systems, one per tool

You’ll now build all three example systems in mypack. Each one is deliberately small; the point is to see the same idea (“remember something”) solved three different ways.

1. A team marker (entity tags)

You already wrote mark_red_team and red_team_glow above, so the tag-based team marker is essentially done. Round it out with a function that reports who’s on the team. The tag=! form is the easy way to count the other side: anything nearby that isn’t red team.

data/mypack/function/team_report.mcfunction

# Announce the two sides
say Red team members nearby:
tag @e[type=minecraft:zombie,tag=red_team,distance=..20] list
say Everyone NOT on red team gets marked blue:
tag @e[type=minecraft:zombie,tag=!red_team,distance=..20] add blue_team

This is the whole shape of a tag system: add to join a group, tag=/tag=! to select members or non-members, remove to leave. No numbers, no files — just labels.

2. A countdown timer (scoreboards)

A timer is a number that changes over time, so it’s a scoreboard job. From Chapter 11 you know how to create an objective and change a holder’s score; here you’ll store the time on a fake player (a made-up score-holder name that isn’t a real player) called #timer, and show it on the sidebar.

First, a setup function that creates the objective and starts the clock at 10:

data/mypack/function/start_timer.mcfunction

# Create the timer objective (safe to run again; errors are harmless if it exists)
scoreboard objectives add mypack_timer dummy
# Show it on the sidebar
scoreboard objectives setdisplay sidebar mypack_timer
# Start the countdown at 10 on a fake player
scoreboard players set #timer mypack_timer 10
say Timer started at 10.

Then a tick step that subtracts one each time it runs. You met the tick function tag in Chapter 11 (mypack/data/minecraft/tags/function/tick.json), so append this function’s id to that file’s values rather than creating a new tick tag:

data/mypack/function/timer_tick.mcfunction

# Subtract 1 from the timer every time this runs
scoreboard players remove #timer mypack_timer 1

mypack/data/minecraft/tags/function/tick.json

{
  "values": [
    "mypack:kill_on_gold",
    "mypack:timer_tick"
  ]
}

(The mypack:kill_on_gold line was already in your tick tag from Chapter 11; you’re adding mypack:timer_tick beside it, not replacing the file.) Run mypack:start_timer and watch the number on the right side of your screen tick down. This is the scoreboard’s home turf: a whole number you can change, compare with /execute if score (Chapter 11), and display, none of which a tag or storage does as neatly.

Try It! Right now the timer keeps counting past zero into negative numbers. With /execute if score from Chapter 11 you can stop it at zero and fire off an event, a great mini-exercise once you’ve finished this chapter.

3. A quest tracker (command storage)

A quest has structure (a stage name, maybe a count of items collected, maybe a list of objectives), so it’s a command-storage job. From Chapter 12 you know that command storage is a general-purpose, namespaced container identified by a resource location like mypack:quest. Here you’ll start a quest by writing a small structured value into storage:

data/mypack/function/quest_start.mcfunction

# Begin the quest: store a stage name and a starting count
data modify storage mypack:quest stage set value "find_the_key"
data modify storage mypack:quest items_collected set value 0
say Quest started: find the key.

Then a function that advances the quest by reading the data back and moving to the next stage:

data/mypack/function/quest_advance.mcfunction

# Move the quest to its next stage and show the stored data
data modify storage mypack:quest stage set value "open_the_door"
data get storage mypack:quest

Run mypack:quest_start, then mypack:quest_advance, and the data get line prints the whole quest object back to you so you can see the structure you’ve stored. Notice what storage gives you that the other two tools don’t: a named value with a stage string and a number side by side, all under one tidy id, with no entity and no fake-player tricks. That’s exactly the “complex or structured data” row of the decision guide.

Three systems, three tools, one lesson: match the tool to the shape of what you’re remembering: a flag, a number, or structured data.

What Can Go Wrong

  • The tag selector matches nothing. Tags are case-sensitive, so @e[tag=Red_Team] will not find entities you tagged red_team. Check the exact spelling and capitalization in your /tag add. Also remember every filter in a selector must hold at once: @e[type=minecraft:zombie,tag=red_team] only matches entities that are both zombies and tagged, so if you tagged a skeleton, that bracket skips it.

  • You used a tag where you needed a number (or vice versa). If you find yourself adding tags like score_1, score_2, score_3 to fake a count, stop. That’s a scoreboard’s job. And if you set a scoreboard objective to 1 or 0 just to mean “done / not done,” that’s really a flag, so an entity tag is simpler. When a design feels awkward, re-check it against the decision guide: numbers → scoreboard, structured data → storage, on/off → tag.

  • You confused the two kinds of tag. Trying to write @e[tag=#minecraft:logs] won’t work: #minecraft:logs is a registry tag (a group of block types, Chapter 14), not an entity tag (a runtime label, this chapter). The tag= selector argument only reads the on/off labels you add with /tag. Keep the two meanings of “tag” apart and this class of bug disappears.

What You Know Now

You can now add a plain on/off label to any entity with tag <targets> add <name>, take it off with remove, and inspect it with list; select labeled entities with @e[tag=...], its negation tag=!, and the empty tag=/tag=! forms; and stack tag filters knowing they all must hold at once. Just as importantly, you can now choose among Part IV’s three tools: command storage for complex or structured data, a scoreboard for an integer you want to count or display, and an entity tag for a simple on/off flag that rides along on a specific entity. And you can tell an entity tag (a runtime sticker on one entity) apart from a registry tag (a file grouping types of things), the distinction that carries you straight into the next chapter, where the other kind of tag takes center stage.

Chapter 14 — Tags: Grouping Things Together

What You’ll Build

Part V is where your data pack stops being mostly commands and starts being mostly files: JSON files that quietly tell the game what things are, what they belong to, and what should happen. The very first of those file types is the tag, and it’s a good place to start, because you’ve secretly been using tags since Chapter 9 without knowing it.

By the end of this chapter you’ll be able to write a JSON file that groups a bunch of blocks (or items, or kinds of mob, or functions) under one name, refer to that whole group with a single # word, extend a built-in Minecraft group by adding your own things to it, and test the result with a command. To prove it works you’ll add a small file to your mypack pack (a block tag called mypack:my_logs) and then write a function that checks whether the block under your feet belongs to it.

This chapter extends the pack you started in Chapter 9, your mypack pack, and uses the test world you’ve used since Chapter 1. It assumes JSON (Chapter 8), the pack’s folder layout and the minecraft:load function tag (Chapter 9), and /execute ... if block ... run from Chapter 4.

First: this is not the tag from last chapter

Chapter 13 ended with a warning, and this chapter is exactly why. The word tag means two completely different things in Minecraft, and we have now arrived at the second one. Before anything else, let’s nail the difference down so it never trips you up.

Modern Minecraft Two unrelated features share the name tag. Tutorials online almost never warn you:

  1. Entity tag (Chapter 13) — a runtime label you stick on one specific entity with the /tag command and check with @e[tag=...]. It’s a sticker on a single mob or player, added and removed live while you play. It has no # and lives in no file.
  2. Registry tag (this chapter) — a JSON file in your data pack that groups whole types of things: every kind of log block, every kind of plank, a list of functions. You refer to it with a leading #, like #minecraft:logs, and you can’t change it with a command mid-game. You edit the file and /reload.

Quick test: red_team stuck on a particular zombie is an entity tag. #minecraft:logs standing for all log block types is a registry tag. Same word, two worlds. This whole chapter is about the second kind.

The divide is clear: an entity tag is a label applied to a single entity at runtime, while registry tags are applied to entity types and can’t be changed by commands. So from here on, when you see a # in front of a name, think registry tag, a group of types defined in a file, never the on/off sticker from last chapter.

What a registry tag actually is

A registry tag is a named list of game things of one kind, grouped so you can talk about all of them at once. Tags let you group different game elements together; they reference groups of registry entries, so Minecraft treats multiple items, blocks, or entities as a single category. The built-in tag #minecraft:logs, for example, stands for all log blocks at once, so a command or recipe that uses #minecraft:logs automatically applies to oak logs, birch logs, cherry logs, and every other block on the list, with no need to spell each one out.

Two things make tags one of the most useful pieces of a data pack:

  • You name a group once and reuse it everywhere. Instead of listing twenty blocks in five different places, you list them once in a tag and point everything at #yourname:yourtag.
  • You can change how the game itself behaves by editing its tags. Minecraft’s own rules lean on built-in tags all over the place: which blocks a tool mines faster, which blocks bees pollinate, which mobs burn in sunlight. Add a block to the right vanilla tag in your pack and you bend that rule without touching a single line of the game’s code.

The word “registry” comes back from Chapter 7: a registry is the game’s master list of one kind of thing (all blocks, all items, all entity types, and so on). A registry tag is just a smaller named list picked out of one of those master lists: a group of blocks out of all blocks, a group of items out of all items. That’s why there’s a different kind of tag for each registry.

Where tags live: data/<namespace>/tags/<registry>/

Tags are files in your data pack, and they go in a tags folder. Here is the layout:

mypack/
  pack.mcmeta
  data/
    <namespace>/
      tags/
        function/
          <name>.json      a function tag
        <registry>/
          <name>.json      a tag for that registry

Inside tags/ you make one folder per registry, and its name is the registry’s name. The four you’ll meet in this chapter are:

  • tags/block/: block tags, groups of block types (like #minecraft:logs).
  • tags/item/: item tags, groups of item types (like #minecraft:planks, used by recipes that accept “any plank”).
  • tags/entity_type/: entity type tags, groups of kinds of mob (like a list that means “all zombie types”).
  • tags/function/: function tags, groups of functions, including the two special ones we’re about to revisit.

Modern Minecraft Just like the function and recipe folders from Chapter 9, these folder names are singular: block, item, entity_type, function, not blocks/items/tags/functions. Older guides (and old packs) used plural names. If your tag silently does nothing, a plural folder name is the first thing to check.

The tag’s name comes from where the file sits, exactly like every other identifier in the book. A file at data/mypack/tags/block/my_logs.json is the block tag mypack:my_logs. (A file at data/wiki/tags/block/foo/example.json makes the tag wiki:foo/example; subfolders just become part of the path, same as Chapter 8.) To use a tag, you put a # in front of that name: #mypack:my_logs. The # is what tells the game “this is a tag (a whole group), not a single block.”

The JSON inside a tag file

A tag file is a small JSON object with up to two fields. Here’s the simplest possible block tag, a group of three log blocks, saved into your pack:

data/mypack/tags/block/my_logs.json

{
  "replace": false,
  "values": [
    "minecraft:oak_log",
    "minecraft:birch_log",
    "minecraft:spruce_log"
  ]
}

Two fields, and that’s the whole format:

  • values is the list, an array (Chapter 8) of names. Each name is a thing you’re putting in the group. Here they’re three block identifiers, so this tag means “oak, birch, or spruce log.”
  • replace is a true/false switch we’ll explain in a moment. It’s optional and defaults to false, so you can leave it out, but we’ll write it in for now to make it visible.

A values list can hold three kinds of entry:

  1. A plain resource location: the name of a thing to include, like "minecraft:oak_log".
  2. Another tag, prefixed with #, which pulls in everything that tag contains. So a values list can contain "#minecraft:planks", meaning “and also every plank.” (Tags referencing tags is allowed and handy; just don’t make a loop where two tags point at each other, which causes a loading failure.)
  3. An object with options, for the one special case in the next callout.

Here’s an entry of type 2 in action, a tag that says “all my logs, plus everything already in the vanilla planks group”:

data/mypack/tags/block/my_building_blocks.json

{
  "replace": false,
  "values": [
    "#mypack:my_logs",
    "#minecraft:planks"
  ]
}

Because both entries start with #, this tag is built entirely out of other tags: it folds in your own mypack:my_logs and the built-in minecraft:planks and treats them as one big group. That’s the “name a group once, reuse it everywhere” idea taken one step further: groups made of groups.

Under the Hood The third kind of values entry is an object with two fields, used when you want to list something that might not exist (say, a block from a mod the player may not have installed):

{
  "values": [
    { "id": "mypack:future_block", "required": false }
  ]
}

Here, id is the name (in any of the formats above), and required decides whether the whole tag fails to load if that entry is missing. It’s true by default; setting it to false means “skip this one quietly if it isn’t found” instead of breaking the tag. You won’t need this often as a beginner, but it’s why you sometimes see entries written as objects rather than plain strings. (Skippable.)

The big reveal: load and tick were tags all along

Open up your pack and look at a file you wrote back in Chapter 9:

data/minecraft/tags/function/load.json

{
  "values": [
    "mypack:load",
    "mypack:score_setup"
  ]
}

Look at the path. Look at the contents. It’s a file in tags/function/, with a values list of function names. It’s a registry tag (a function tag), and you’ve been writing them since your very first data pack. You just didn’t have the word for it yet.

Here’s what these two special function tags do: functions tagged in the minecraft:tick tag run every tick at the start of the tick, and functions tagged in minecraft:load run once at the start of the tick after a server (re)load. That’s exactly the behavior you’ve relied on. The file at data/minecraft/tags/function/load.json is the minecraft:load tag (it’s in the minecraft namespace because that’s the folder it sits in), and listing mypack:load in its values is how you told the game “run my load function when the pack loads.” Same machinery for the tick tag you started in Chapter 11:

data/minecraft/tags/function/tick.json

{
  "values": [
    "mypack:kill_on_gold",
    "mypack:timer_tick"
  ]
}

Every function you’ve ever hooked into “run on load” or “run every tick” you did by adding its name to a values list in a function tag. The only thing new in this chapter is the name for what you were doing, plus the fact that the same trick works for blocks, items, and mob types too.

Under the Hood Function tags run their functions in the order of their first appearance in a tag, and if the same function is listed twice (directly or through a sub-tag) it still runs only once. For load and tick that ordering is the one place where the order of a tag’s values actually matters. For most tags (which only ever get asked “is this thing in the group, yes or no?”) the order is irrelevant. (Skippable.)

Extending a vanilla tag vs. replacing it

Now back to that replace field, because it controls the single most powerful thing tags can do: quietly add your stuff to a built-in Minecraft group.

Here’s the key rule. When two data packs (and remember, vanilla itself is a data pack) define a tag with the same name:

  • If replace is false (or left out, which is the default), your values are added on top of what’s already there. You extend the group.
  • If replace is true, your tag completely replaces the lower-priority one. You wipe out everything that was in it and start fresh with only your values.

So to add a block to a vanilla tag, you make a file with the same name as the vanilla tag (same namespace, same path) and leave replace off. Watch: this adds cherry logs to Minecraft’s own #minecraft:logs group, without disturbing the dozens of logs already in it:

data/minecraft/tags/block/logs.json

{
  "replace": false,
  "values": [
    "minecraft:cherry_log"
  ]
}

Notice the namespace is minecraft, not mypack: the file lives at data/minecraft/tags/block/logs.json because it’s the minecraft:logs tag you’re extending. With replace false, oak, birch, spruce, and the rest stay exactly where they are; your one line just joins the party. From now on, anything in the game that checks #minecraft:logs will treat cherry logs as a log too.

Flip replace to true and the meaning changes completely:

data/minecraft/tags/block/logs.json

{
  "replace": true,
  "values": [
    "minecraft:cherry_log"
  ]
}

This version says “#minecraft:logs now means only cherry log, forget everything else.” Every other log type falls out of the tag, and any game behavior tied to #minecraft:logs suddenly ignores them. That’s almost never what you want for a vanilla tag, but it’s exactly what you want when you’re defining your own fresh tag and don’t care about merging with anything. Rule of thumb: extend (false / leave it off) when touching a vanilla tag; only reach for replace: true when you mean to throw the old contents away.

A few vanilla tags worth knowing, all real groups you can extend or point at:

  • #minecraft:logs: every log block.
  • #minecraft:planks: every plank block (and the matching item tag is used by recipes that take “any plank”).
  • #minecraft:leaves, #minecraft:wool: leaves and wool blocks.
  • #minecraft:swords: the item tag grouping all swords.

Walkthrough: build and test a block tag

Let’s put it all together. You’ll make a block tag and then write a command that checks it.

Step 1 — create the tag. If you followed along above, you already have it; if not, save this:

data/mypack/tags/block/my_logs.json

{
  "replace": false,
  "values": [
    "minecraft:oak_log",
    "minecraft:birch_log",
    "minecraft:spruce_log"
  ]
}

Step 2 — write a function that tests it. Back in Chapter 4 you learned /execute ... if block ... run, which checks the block at a position. The block you test for can be a single block ID or a block tag, and to use a tag you write it with a #, just like everywhere else. This function checks whether the block one space below you is in your my_logs group:

data/mypack/function/tag_check.mcfunction

# Is the block under my feet one of my logs?
execute if block ~ ~-1 ~ #mypack:my_logs run say Standing on a my_logs block!

The position ~ ~-1 ~ is “right where I am, but one block down” (relative coordinates from Chapter 2), and #mypack:my_logs is your tag. The command succeeds (and the say runs) only if the block down there is oak, birch, or spruce log, because those are the three types your tag groups.

Step 3 — try it. Run /reload so the game reads your new files, place an oak log, stand on top of it, and run the function from chat with /function mypack:tag_check. You should see the message. Step off onto plain dirt and run it again: silence, because dirt isn’t in the tag. One tag, three block types, one tidy check.

Figure (to be captured). chat shows “Standing on a my_logs block!” after running mypack:tag_check while the player stands on top of an oak log block

Try It! Add a fourth log to your tag’s values (say "minecraft:cherry_log"), /reload, and your same tag_check function now recognizes cherry logs too. You changed what the check matches without touching the function at all. That’s the whole point of tags: edit the group in one place, and everything that points at #mypack:my_logs updates at once.

Practice

  1. Make an item tag. Create data/mypack/tags/item/my_gems.json grouping "minecraft:diamond" and "minecraft:emerald". Item tags can be searched in the Creative inventory by typing #mypack:my_gems in the search box; try it and watch both gems appear.

  2. Extend a real vanilla group. Pick a vanilla tag from the list above and add a block to it the extend way (matching namespace and path, replace left off). For example, drop something into data/minecraft/tags/block/wool.json. /reload and confirm the original contents are still there by checking a block that was already in the tag.

  3. Build a tag out of tags. Write a block tag whose values are nothing but two # references (one to your own mypack:my_logs and one to a vanilla tag) and use it in a tag_check-style function. Confirm that the combined group matches blocks from both source tags.

What Can Go Wrong

  • You forgot the #. Inside a command, #mypack:my_logs means “the tag,” but mypack:my_logs with no # means “a single block named mypack:my_logs,” which doesn’t exist, so the command errors or never matches. The # is what turns a name into a group. (And the opposite mistake from last chapter: don’t put a # on an entity tag; @e[tag=red_team] never takes one.)

  • replace: true wiped a vanilla tag. If a built-in behavior suddenly stops working after you edited a vanilla tag (bees ignoring your flowers, a tool no longer mining fast), check whether you left replace set to true. On a vanilla tag that throws away everything the game put there. Switch it to false (or delete the line) so your entry is added instead of substituted, then /reload.

  • Wrong folder, or a plural folder name. A block tag must sit in tags/block/, an item tag in tags/item/, and so on. Put it in the wrong registry folder and it groups the wrong kind of thing (or nothing). And the folder names are singular: tags/block/, never tags/blocks/. A silently-ignored tag is almost always a misspelled or mis-pluralized folder.

  • An entry doesn’t exist and the whole tag breaks. If a name in values points at something the game can’t find, loading the tag fails. Either fix the spelling, or, if it’s deliberately optional, use the object form { "id": "...", "required": false } so that one missing entry is skipped instead of breaking the rest.

What You Know Now

You can now write a registry tag: a JSON file under data/<namespace>/tags/<registry>/ that groups types of things (blocks, items, entity types, or functions) under one name you address with a #. You know the format is just values (the list) and an optional replace flag; that values entries can be plain names, other tags prefixed with #, or { "id": ..., "required": false } objects; and that replace: false extends a tag while replace: true replaces it, which is how you safely add your own blocks to Minecraft’s own groups. You finally know what those load.json and tick.json files have been all along: function tags, the same mechanism wearing a special name. And you can test a block tag in-game with /execute if block ~ ~-1 ~ #mypack:my_logs. Tags are the quiet backbone of the declarative files coming next (recipes, loot tables, advancements, and more all lean on them), so this is the tool that makes the rest of Part V click.

Chapter 15 — Recipes

What You’ll Build

Way back in Chapter 9 you wrote a single recipe: a chainmail helmet crafted from iron nuggets. It worked, it showed up in the recipe book, and then you moved on. That recipe used just one of the many kinds of recipe Minecraft understands. This chapter opens the whole box.

By the end you’ll be able to add almost any kind of recipe to the pack you started in Chapter 9, your mypack pack. You’ll write shaped and shapeless crafting recipes; smelting, blasting, smoking, and campfire cooking recipes (with their own cook times and experience rewards); stonecutting recipes; both kinds of smithing recipe; the special transmute and dye recipes; and you’ll make any of them accept “any kind of wood” or “any copper ore” by feeding them the registry tags you learned in Chapter 14. You’ll also learn how to remove a vanilla recipe you don’t want, and how to control where your recipes appear in the recipe book.

This chapter extends the mypack pack from Chapter 9 and uses the test world you set up in Chapter 8. It assumes you know the recipe/ folder and the shaped recipe from Chapter 9, and registry tags (#namespace:path) from Chapter 14.

Modern Minecraft Every recipe in this chapter puts its output in a result object whose item-ID field is named id. Older tutorials (and packs written before this changed) use a field called item instead. If you copy a recipe off the internet and it doesn’t load, the first thing to check is whether it says "item": where it should say "id":. Throughout this book we always use id.

Where recipes live (a quick recap)

Every recipe is a single JSON file. Your custom recipe files go in the recipe folder of your namespace: a recipe with the ID mypack:path/to/file lives at data/mypack/recipe/path/to/file.json inside the pack. That’s exactly where the Chapter 9 helmet recipe sits. Vanilla’s own recipes live in the same kind of place but under the minecraft namespace (data/minecraft/recipe/...), a fact we’ll use later in this chapter to switch one off.

Every recipe file, no matter its kind, is a JSON object with one field in common: a string called type. The type tells the game which shape of recipe this is (shaped crafting, smelting, stonecutting, and so on) and therefore which other fields it should expect. Get the type right and the rest of the file follows from it. The sections below are organized one type at a time.

Shaped crafting, revisited

A shaped crafting recipe is one where the ingredients have to be arranged in a particular shape on the crafting grid. The helmet you made in Chapter 9 is one: the iron nuggets have to sit in an upside-down-U shape or the recipe won’t match. Here it is again, unchanged, so we have something to build on:

mypack/data/mypack/recipe/chainmail_helmet.json

{
  "type": "minecraft:crafting_shaped",
  "category": "equipment",
  "pattern": [
    "NNN",
    "N N"
  ],
  "key": {
    "N": "minecraft:iron_nugget"
  },
  "result": {
    "id": "minecraft:chainmail_helmet",
    "count": 1
  }
}

Let’s read it slowly, because the two fields that make it shaped (pattern and key) work together as a pair.

pattern is a list of strings, one per row of the crafting grid. Each string is a row, and each character in the string is one grid slot. The recipe above has two rows, "NNN" and "N N", so it uses a 3-wide, 2-tall area of the grid. Every string in the list must be the same length. A row can be 1, 2, or 3 characters wide, and you can have 1, 2, or 3 rows.

key is an object that says what each character means. Here, the single key N maps to minecraft:iron_nugget. Any single character may be used as a key except the space character: a space in the pattern always means “this slot must be empty.” That’s why "N N" has a real gap in the middle: the helmet has a hole in its bottom row.

The category field is new compared to Chapter 9’s bare version. It controls which tab the recipe shows up under in the recipe book. For crafting recipes the allowed values are equipment, building, misc, and redstone; if you leave category out, it defaults to misc. Armor is equipment, so the helmet sits in the equipment tab.

The result object is the output. Its id is the item the recipe makes, and the optional count is how many you get. If you leave count out, you get 1. We’ve written "count": 1 explicitly here just to show where it goes.

Try It! Change the result’s count to 4 and /reload. Now one grid of nuggets gives you four helmets. Silly, but it proves the field works. Set it back to 1 when you’re done.

Grouping recipes with group

There’s one more optional field worth knowing for crafting recipes: group. It’s a plain string identifier, and recipes that share the same group are bundled together into a single entry in the recipe book instead of cluttering it with near-duplicates. Vanilla uses this for things like all the different wooden planks, which share a planks group. You don’t have to use it, but it keeps a busy recipe book tidy. (A subtle detail: if two recipes share a group but have different category values, the game splits them back into two groups, so keep the category the same across a group.)

Shapeless crafting

A shapeless crafting recipe doesn’t care where the ingredients sit on the grid, only that they are all present. The classic example is flint and steel: you need one iron ingot and one piece of flint, and it doesn’t matter which slots they go in. Its type is minecraft:crafting_shapeless, and instead of pattern/key it has a single list field called ingredients:

mypack/data/mypack/recipe/flint_and_steel.json

{
  "type": "minecraft:crafting_shapeless",
  "category": "equipment",
  "ingredients": [
    "minecraft:iron_ingot",
    "minecraft:flint"
  ],
  "result": {
    "id": "minecraft:flint_and_steel",
    "count": 1
  }
}

The ingredients list must have at least one and at most nine entries, one for every slot in the 3×3 grid. Each entry is one ingredient that must appear somewhere in the grid. The same category values, the optional group, and the same result shape (id + optional count) all work exactly as they did for shaped recipes.

Under the Hood There’s a small quirk worth knowing: if a shapeless recipe lists the same nine ingredients as a shaped recipe’s full grid, the game effectively treats it as the shaped one. You’ll almost never hit this; it’s here so the behavior doesn’t surprise you. Skippable.

Ingredients, three ways

So far every ingredient has been a single item ID like "minecraft:iron_ingot". But anywhere a recipe asks for an ingredient, you actually have three ways to write it. This is the same in every recipe type, so learning it once pays off everywhere.

1. A single item ID: a plain string, the form you’ve already seen:

"minecraft:iron_ingot"

2. A tag, written with a leading #. This is where Chapter 14 pays off. A #tag means “any item in this group counts.” Remember from Chapter 14 that a registry tag is a named group of types; #minecraft:planks is the built-in group of every plank variant. Use it as an ingredient and your recipe accepts oak, birch, spruce, or any other plank without you listing them:

"#minecraft:planks"

3. An array of IDs: a JSON list of specific item IDs, meaning “any one of exactly these”:

["minecraft:oak_planks", "minecraft:spruce_planks"]

The difference between forms 2 and 3 is how you choose the set. A #tag borrows an existing group (or one you defined yourself in Chapter 14) and stays in sync with it. An array spells out an exact short list right there in the recipe. Use a tag when a sensible group already exists; use an array when you want just two or three specific items and don’t want to make a whole tag file for them.

Here’s a shapeless recipe that uses a tag ingredient, a custom “wooden button” that accepts any plank:

mypack/data/mypack/recipe/any_plank_button.json

{
  "type": "minecraft:crafting_shapeless",
  "category": "redstone",
  "ingredients": [
    "#minecraft:planks"
  ],
  "result": {
    "id": "minecraft:oak_button",
    "count": 1
  }
}

And the same idea with an explicit array, accepting only oak or spruce:

mypack/data/mypack/recipe/oak_or_spruce_button.json

{
  "type": "minecraft:crafting_shapeless",
  "category": "redstone",
  "ingredients": [
    ["minecraft:oak_planks", "minecraft:spruce_planks"]
  ],
  "result": {
    "id": "minecraft:oak_button",
    "count": 1
  }
}

Notice the array in the second file is nested inside the ingredients list: ingredients is the outer list (one entry per grid slot), and that one entry is itself the little array of acceptable items. In a shaped recipe you’d do the same thing inside key: a key can map to a single ID, a #tag, or an array, just like an ingredient. The three forms are completely interchangeable everywhere an ingredient is asked for.

What Can Go Wrong Forgetting the # on a tag is the most common recipe mistake. "minecraft:planks" (no hash) tells the game to look for an item literally named planks, which doesn’t exist, so the recipe fails to load. "#minecraft:planks" (with hash) tells it to look for the tag. One character, big difference, and Chapter 10’s game log will report the bad recipe if you miss it.

Cooking: smelting, blasting, smoking, and campfire

Furnaces, blast furnaces, smokers, and campfires all run on recipes too. They share almost the same shape, differing mainly in their type string and their default cook time:

  • minecraft:smelting — a regular furnace.
  • minecraft:blasting — a blast furnace (ores and metal things).
  • minecraft:smoking — a smoker (food).
  • minecraft:campfire_cooking — a campfire.

A cooking recipe takes a single ingredient (written in any of the three ingredient forms above), turns it into a result, and adds two optional numbers: cookingtime and experience.

Here’s a smelting recipe that turns stone bricks into cracked stone bricks:

mypack/data/mypack/recipe/cracked_stone_bricks.json

{
  "type": "minecraft:smelting",
  "category": "blocks",
  "ingredient": "minecraft:stone_bricks",
  "cookingtime": 200,
  "experience": 0.1,
  "result": {
    "id": "minecraft:cracked_stone_bricks"
  }
}

cookingtime is how long the item takes to cook, measured in ticks (the game runs 20 ticks per second, as you learned in Chapter 7, so 200 ticks is 10 seconds). It’s optional. If you leave it out, smelting defaults to 200 ticks, while blasting, smoking, and campfire_cooking default to 100 ticks. Blast furnaces and smokers run hotter, so even their default is faster.

experience is how much experience the player gets when they collect the cooked item. It’s a decimal number and it’s optional. A small detail to remember: campfires never drop experience, so an experience field on a campfire_cooking recipe simply does nothing.

Notice what the cooking result does not have: a count. Cooking recipes always produce one output item, so their result only ever has id (and, optionally, components, more on that later). Adding a count here won’t help. This is different from crafting and stonecutting, where count is allowed.

The category choices differ a little by machine. Smelting allows food, blocks, and misc (default misc); blasting allows blocks and misc; smoking and campfire both allow only food (default food). For campfires the category and group fields are accepted but do nothing, because campfires have no recipe book.

Here’s a campfire example (baking a potato over a fire) showing the longer cook time vanilla uses:

mypack/data/mypack/recipe/campfire_baked_potato.json

{
  "type": "minecraft:campfire_cooking",
  "category": "food",
  "ingredient": "minecraft:potato",
  "cookingtime": 600,
  "experience": 0.35,
  "result": {
    "id": "minecraft:baked_potato"
  }
}

Stonecutting

A stonecutting recipe is the simplest of all. You put one ingredient into a stonecutter, pick the output from the menu, and it converts instantly: no shape, no cook time, no experience. Its type is minecraft:stonecutting, and it has just two fields you care about: an ingredient and a result. There’s no category or group for stonecutting at all.

mypack/data/mypack/recipe/deepslate_to_stairs.json

{
  "type": "minecraft:stonecutting",
  "ingredient": "minecraft:cobbled_deepslate",
  "result": {
    "id": "minecraft:cobbled_deepslate_stairs",
    "count": 1
  }
}

Stonecutting result objects can carry a count, unlike cooking. That’s handy when one input should yield several outputs.

We’ll come back to stonecutting in the Practice section, where you’ll build a small family of stonecutter recipes for block variants.

Smithing: two very different recipes

The smithing table runs two completely different kinds of recipe, and it’s important not to mix them up. They have different type strings and do different jobs:

  • minecraft:smithing_transform: changes the base item into a new item. This is how a diamond axe becomes a netherite axe.
  • minecraft:smithing_trim: decorates the base item with an armor trim, leaving it the same item with a new pattern stamped on. This is how you add a trim to a chestplate.

Both share three ingredient slots, named for where they sit in the smithing table:

  • template: the smithing template item (optional).
  • base: the item being upgraded or trimmed.
  • addition: the material being added (optional).

smithing_transform — upgrading an item

mypack/data/mypack/recipe/netherite_axe.json

{
  "type": "minecraft:smithing_transform",
  "template": "minecraft:netherite_upgrade_smithing_template",
  "base": "minecraft:diamond_axe",
  "addition": "#minecraft:netherite_tool_materials",
  "result": {
    "id": "minecraft:netherite_axe"
  }
}

A smithing_transform recipe has a result, the new item it produces. The important behavior is that the result copies the components of the base item. So if your diamond axe was enchanted and nearly worn out, the netherite axe that comes out keeps those enchantments and that damage. The addition here uses a #tag ingredient, so any item in the netherite_tool_materials group works.

smithing_trim — stamping a trim

mypack/data/mypack/recipe/silence_trim.json

{
  "type": "minecraft:smithing_trim",
  "template": "minecraft:silence_armor_trim_smithing_template",
  "base": "#minecraft:trimmable_armor",
  "addition": "#minecraft:trim_materials",
  "pattern": "minecraft:silence"
}

The big difference: a smithing_trim recipe has no result field at all. It doesn’t make a new item; it adds the trim’s pattern onto whatever armor went in as the base. Which trim it applies is named by the pattern field (here, minecraft:silence). The base and addition use #tags so the recipe works for any trimmable armor and any trim material. If you write a smithing_trim recipe and try to give it a result, you’ve accidentally written it like a transform. They are not interchangeable.

What Can Go Wrong The two smithing types look similar but answer different questions. Transform answers “what new item does this become?”, so it needs a result. Trim answers “which pattern goes on this armor?”, so it needs a pattern and no result. Pick the type by the question you’re answering.

Transmute and dye: two special crafting types

Two more crafting-table recipe types do things the basic shaped/shapeless types can’t, because they carry information from the input over to the output.

crafting_transmute — change the item, keep its components

A transmute recipe (minecraft:crafting_transmute) changes one item into another while preserving all of the input’s components. Vanilla uses it to re-dye a shulker box: you put in any shulker box plus a dye, and out comes the recolored box with everything still inside it, because the contents are components that get copied across.

It has three fields beyond type: an input (the item to copy), a material (an extra ingredient consumed alongside it), and a result:

mypack/data/mypack/recipe/blue_shulker_box.json

{
  "type": "minecraft:crafting_transmute",
  "category": "misc",
  "group": "shulker_box_dye",
  "input": "#minecraft:shulker_boxes",
  "material": "minecraft:blue_dye",
  "result": {
    "id": "minecraft:blue_shulker_box"
  }
}

The input here is a #tag (any shulker box), the material is a specific dye, and the result is the blue box, which keeps whatever the input box was holding.

crafting_dye — the armor-dyeing recipe

A dye recipe (minecraft:crafting_dye) is the special type behind dyeing leather armor and similar items. It has a target (the item this recipe applies to), a dye ingredient (which dyes are allowed), and a result:

mypack/data/mypack/recipe/dye_leather_horse_armor.json

{
  "type": "minecraft:crafting_dye",
  "category": "misc",
  "group": "dyed_armor",
  "dye": "#minecraft:dyes",
  "target": "minecraft:leather_horse_armor",
  "result": {
    "id": "minecraft:leather_horse_armor"
  }
}

The dye ingredient uses the #minecraft:dyes tag, so any dye works. For this recipe to match, the item being dyed must have the dye-able minecraft:dye component, a detail we won’t go deeper on until components get their own chapter.

What can go on a result

You’ve now seen the two optional result fields several times. Here they are collected in one place:

  • count: how many copies of the item the recipe produces. Allowed on crafting and stonecutting and smithing-transform results; defaults to 1. Not used on cooking results, which always make exactly one item.
  • components: extra data attached to the resulting item, like a custom name, enchantments, or what’s stored inside it.

We won’t unpack components fully here; they get their whole own chapter (Chapter 21). For now, just know the field exists and where it goes. A result with components looks like this in outline:

"result": {
  "id": "minecraft:diamond_sword",
  "count": 1,
  "components": { }
}

Under the Hood A few crafting recipes are not data-driven at all — things like copying a written book or a map, or dyeing armor with several dyes at once. Those use built-in type names that start with crafting_special_ (and there’s a crafting_decorated_pot too). You can’t really author these yourself; they exist so that, if you ever turn off the vanilla data pack, you can switch the built-in recipes back on one by one. You won’t need them for your own recipes. Skippable.

Removing a vanilla recipe

Sometimes you want to make an existing recipe go away instead of adding one. Maybe your pack replaces wooden swords with something custom and you don’t want players crafting the plain one.

The trick relies on how packs overlap. The rule is simple: data packs that load later, with a recipe file at the same resource location, replace the existing recipe. Vanilla’s wooden-sword recipe lives at data/minecraft/recipe/wooden_sword.json. If your pack contains a file at that exact same path (under the minecraft namespace, not mypack), your version wins, because your pack loads after the built-in vanilla pack.

So to disable the wooden sword recipe, put a file at the matching path that defines a recipe which can never be crafted: for example one whose ingredient is an item the player can’t reasonably get, or simply a different, harmless recipe. The cleanest approach players use is to overwrite it with a recipe that produces something trivial. Here’s an override that turns the wooden-sword slot into a recipe requiring a barrier block (a block normal players never have):

mypack/data/minecraft/recipe/wooden_sword.json

{
  "type": "minecraft:crafting_shapeless",
  "category": "misc",
  "ingredients": [
    "minecraft:barrier"
  ],
  "result": {
    "id": "minecraft:wooden_sword",
    "count": 1
  }
}

Because this file sits at vanilla’s resource location and your pack loads later, it replaces vanilla’s wooden-sword recipe. The original recipe is gone; only your (effectively uncraftable) version remains.

Good to Know Overriding a recipe at its resource location, as shown here, is the supported way to take a vanilla recipe out of play: same path, later pack wins. There’s no separate “delete this recipe” or empty-file syntax to learn; the override-with-a-dummy recipe is the clean, intended approach.

Modern Minecraft A recipe being in the game and a recipe being unlocked in your recipe book are two separate things. Players can craft any loaded recipe whose ingredients they hold, whether or not it’s been “discovered,” unless a world turns on the doLimitedCrafting game rule; then only unlocked recipes can be crafted. You can hand out a recipe with the /recipe give command, but most packs let advancements unlock recipes automatically; that’s a Chapter 19 topic.

Practice: a stonecutter for custom block variants

Time to build something. Imagine mypack adds a decorative theme based on polished blackstone, and you want the stonecutter to offer a tidy little family of cuts from a single block of polished blackstone bricks. Stonecutting is perfect for this: one input, many possible outputs, instant conversion.

Make three stonecutting recipes. Each takes minecraft:polished_blackstone_bricks and produces a different variant.

mypack/data/mypack/recipe/blackstone_brick_slab.json

{
  "type": "minecraft:stonecutting",
  "ingredient": "minecraft:polished_blackstone_bricks",
  "result": {
    "id": "minecraft:polished_blackstone_brick_slab",
    "count": 2
  }
}

mypack/data/mypack/recipe/blackstone_brick_stairs.json

{
  "type": "minecraft:stonecutting",
  "ingredient": "minecraft:polished_blackstone_bricks",
  "result": {
    "id": "minecraft:polished_blackstone_brick_stairs",
    "count": 1
  }
}

mypack/data/mypack/recipe/blackstone_brick_wall.json

{
  "type": "minecraft:stonecutting",
  "ingredient": "minecraft:polished_blackstone_bricks",
  "result": {
    "id": "minecraft:polished_blackstone_brick_wall",
    "count": 1
  }
}

Notice the slab recipe produces a count of 2: one brick block cuts into two slabs, which matches how Minecraft normally trades blocks for slabs. The stairs and wall give one each.

Now load it:

  1. Save all three files.
  2. In your test world, run /reload (typed in chat, with the slash) to re-read the pack.
  3. Open a stonecutter, place a polished blackstone brick block in it, and you should see all three cuts offered. Pick one.

Figure (to be captured). a stonecutter menu showing the three polished-blackstone-brick variants — slab, stairs, and wall — offered from a single input block

Try It! Give one of your stonecutting recipes a #tag ingredient instead of the single block ID. If you made a custom tag in Chapter 14 that groups several “brick”-style blocks, point the recipe at #mypack:your_tag and a single stonecutter recipe will now accept any of them.

What Can Go Wrong

You wrote "item" instead of "id" in the result. This is the number-one recipe bug, especially when copying older tutorials. The modern field name is id. A recipe with "item": in its result won’t load; Chapter 10’s game log will flag it on /reload.

You put a count on a cooking result. Smelting, blasting, smoking, and campfire results take only id (and optional components), never count. They always make one item. If you expected a cooking recipe to output a stack, that isn’t how cooking works.

You forgot the # before a tag. "#minecraft:planks" is the tag (any plank); "minecraft:planks" (no hash) is read as an item literally named “planks,” which doesn’t exist, so the recipe silently fails to load. Whenever an ingredient should mean “any of a group,” check for the hash.

You mixed up the two smithing types. A smithing_trim recipe must not have a result; a smithing_transform recipe must. If your trim recipe isn’t trimming, check that you used minecraft:smithing_trim with a pattern and no result.

What You Know Now

You can now write recipes of every major family: shaped and shapeless crafting; smelting, blasting, smoking, and campfire cooking (with cook times and experience); stonecutting; both smithing_transform and smithing_trim; and the special crafting_transmute and crafting_dye types. You know the three ways to write any ingredient (a single ID, a #tag that borrows a Chapter-14 group, or an array of IDs) and that those forms are interchangeable everywhere. You can set a recipe’s category and group to place it neatly in the recipe book, put a count on the results that allow one, and even remove a vanilla recipe by overriding its file path. You used all of this to give mypack a stonecutter family for custom block variants. The one thing we deferred, the components you can attach to a result, gets its own chapter soon, when you learn what data components really are.

Chapter 16 — Loot Tables: Pools, Entries, and Weights

What You’ll Build

Every time a chest in a dungeon hands you a surprise, a zombie drops rotten flesh, or your fishing rod hooks an enchanted book, Minecraft is reading a loot table, a JSON file that decides which items appear in that situation. In this chapter you’ll learn how a loot table is put together, and then you’ll write one of your own: a treasure chest for your mypack pack that almost always gives a common item but, once in a while, drops something rare.

By the end you’ll be able to read and write the three nested pieces every loot table is built from (pools, a group of possible drops; entries, the individual things that can drop; and weight, the dial that makes some entries rarer than others), and you’ll know how to make a count exact, random within a range, or random-but-clustered using a number provider.

This chapter leans on the registry tags you learned in Chapter 14 (those #namespace:path groups of item types, like #minecraft:planks) because a loot table can drop a whole tag at once. It extends the mypack pack you started in Chapter 9 and uses the test world from Chapter 1. Loot tables can do even more than choosing items: they can attach conditions (“only if killed by a player”) and run functions (“set the count to 3, add an enchantment”). Those two powers are big enough to get their own chapter, so we’ll preview them at the end and teach them properly in Chapter 17. This chapter is about getting the structure rock-solid first.

What a loot table is, and what it controls

A loot table is a technical JSON file used to dictate what items should generate in various situations, such as what items should be in naturally generated containers, what items should drop when breaking a block or killing a mob, what items can be fished, and more. One file, asked over and over, “what items come out this time?”

The “situation” a loot table runs in has a name: it’s called the loot context. You don’t have to memorize the list, but it’s worth seeing how many corners of the game loot tables quietly run. A loot table can be invoked for:

  • Container contents: opening a barrel, chest, trapped chest, hopper, minecart with chest, boat with chest, minecart with hopper, dispenser, dropper, shulker box, dyed shulker box, or decorated pot.
  • Mob drops: the loot from a living entity’s death.
  • Block drops: the items dropped when mining a block (or when a block is exploded).
  • Fishing: the items obtained via fishing.
  • Gifts: a gift from a cat, villager, or sniffer, and the item laid by a chicken.
  • Archaeology: using a brush on suspicious sand or suspicious gravel that has a loot table.

There are more (bartering with piglins, shearing certain mobs, vault loot, advancement rewards), but those six cover the situations a beginner meets first. The important takeaway: a chest’s loot and a zombie’s drops are data, and data packs can change or add them.

Modern Minecraft A few drops are not loot tables. Loot tables do not affect dropped experience, and don’t cover dropped non-item entities such as slimes from larger slimes or silverfish from infested blocks. Some unbreakable blocks (bedrock, end portals) have no loot table at all, and a few drops like the wither’s nether star are handled specially. If a drop seems to ignore your loot table, it may be one of these exceptions.

Where loot tables live

Like every other kind of file in a data pack, a loot table lives in a fixed folder. Loot tables are defined using JSON files stored within a data pack in the path data/<namespace>/loot_table.

So inside your mypack pack, your loot tables go here:

mypack/data/mypack/loot_table/

Just like the function, recipe, and tags/function folders you’ve already used, the folder name is singular: loot_table, not loot_tables. (Older tutorials from before the great folder-renaming will show you the plural. It won’t work today. See “What Can Go Wrong.”)

The shape of a loot table: pools

The top level of a loot table file is a JSON object. The field that does the real work is pools, a list of all pools for this loot table. Pools are applied in order.

A pool is one group of possible drops. Think of a pool as a single bag you reach into. A loot table can have several bags (several pools), and when the loot table runs, it reaches into every bag. Here’s the smallest useful loot table: one pool, holding one possible drop.

mypack/data/mypack/loot_table/treasure_chest.json

{
  "pools": [
    {
      "rolls": 1,
      "entries": [
        {
          "type": "minecraft:item",
          "name": "minecraft:bread"
        }
      ]
    }
  ]
}

Notice pools is an array (square brackets) because there can be more than one pool, and inside it each pool is its own object. Two fields appear in that pool, and they’re the heart of this whole chapter: rolls and entries.

Rolls: how many times you reach in

The rolls field is a number provider that specifies the number of rolls on the pool. A roll is one draw: in each roll of a pool, the pool draws one entry from all its entries, and each roll of a pool is independent. So "rolls": 1 means reach into this bag once and pull out one entry. "rolls": 3 would reach in three separate times, and because each roll is independent, you could pull the same entry more than once.

For now we’re writing rolls as a plain number. Later in the chapter you’ll see that rolls can also be a random count, which is where number providers come in.

Entries: what’s in the bag

The entries field is the list of things that could come out of a pool. One loot entry is chosen per roll as a weighted random selection from all loot entries in the pool. So each roll picks exactly one entry from this list. Get the list right and you control everything the pool can produce.

The four entry types

Every entry has a type field, a resource location naming what kind of entry it is. There are several types; this chapter uses the four most useful ones. Each is shown below as a complete entry object.

item — drop one specific item. It drops a single item stack (the default is a stack of 1 of the item). It needs a name field, the resource location of the item:

{
  "type": "minecraft:item",
  "name": "minecraft:diamond"
}

tag — drop items from a registry tag. This is where Chapter 14 pays off. The tag entry’s name is “the resource location of the item tag to query, e.g. minecraft:arrows.” It has a second field, expand, a true/false switch:

{
  "type": "minecraft:tag",
  "name": "minecraft:arrows",
  "expand": false
}

Here’s what the switch does precisely. With "expand": false it is a singleton entry that drops all items in the tag: pick this entry on a roll and you get one of each item in the tag. With "expand": true it provides one singleton entry per item in the tag with the same weight, so the tag fans out and the roll picks just one item from the tag at random. For a treasure chest you almost always want true, so each roll gives a single random arrow type rather than every arrow at once.

loot_table — drop the result of another loot table. This lets you reuse a table inside another. Its field is value, the loot table to be used. Point it at one of your own tables, for example a mypack:rare_drops table you’ve written in the same loot_table folder:

{
  "type": "minecraft:loot_table",
  "value": "mypack:rare_drops"
}

One rule: the value cannot be the ID of the current loot table file. Recursive calling is not allowed, so a table can’t include itself, or you’d get an infinite loop.

empty — drop nothing. It drops nothing, and takes no extra fields:

{
  "type": "minecraft:empty"
}

An empty entry sounds pointless, but it’s the secret to making a pool sometimes give nothing, which you’ll use in a moment.

Weight: making some entries rarer

If a pool has several entries, how does it choose between them on each roll? By weight. Every item (and tag) entry can carry a weight field. It determines how often this singleton entry is chosen out of all the singleton entries in the pool: entries with higher weights are used more often. If you leave weight off, it defaults to 1.

The exact rule is a simple fraction:

The chance of an entry being chosen is [this entry’s weight ÷ total weight of all entries in the pool].

So weight is a share, not a percentage. Suppose a pool has three entries with weights 10, 1, and

  1. The total weight is 12, so the first entry is chosen 10⁄12 of the time (about 83%), and the other two are 1⁄12 each (about 8%). Want something rarer? Give it a smaller weight relative to the others. Here’s a pool that hands out bread most of the time and a diamond rarely:
{
  "rolls": 1,
  "entries": [
    {
      "type": "minecraft:item",
      "name": "minecraft:bread",
      "weight": 20
    },
    {
      "type": "minecraft:item",
      "name": "minecraft:diamond",
      "weight": 1
    }
  ]
}

The total weight is 21, so each roll gives bread 20⁄21 of the time and a diamond 1⁄21 of the time, roughly a 1-in-21 treasure.

Try It! Add a third entry to that pool, an empty entry with "weight": 9. Now the totals are bread 20, empty 9, diamond 1 (total 30), so a roll gives nothing 9⁄30 (about 30%) of the time. Mixing in empty is exactly how vanilla chests leave some slots blank.

Number providers: exact, range, or clustered

Back to rolls. So far it’s been a plain number like 1. But rolls is a number provider, the game’s flexible way to supply a number that can be fixed or random. There are three you’ll reach for, plus a couple of advanced ones we’ll save for later.

constant — an exact value. This is what a plain number already is. The long form names a type and gives the exact number in a value field:

{
  "type": "minecraft:constant",
  "value": 3
}

Writing "rolls": 3 is just the shorthand for this. Most of the time the shorthand is all you need.

uniform — a random number in a range. This is a random number following a uniform distribution between two values (inclusive). It takes a min and a max, and every number in between is equally likely:

{
  "type": "minecraft:uniform",
  "min": 1,
  "max": 4
}

Use this for "rolls" when you want a chest to hold a varying number of drops (here, anywhere from 1 to 4). There’s also a handy shorthand: a bare { "min": ..., "max": ... } object (with no type) is automatically treated as a uniform distribution.

binomial — a clustered random number. This is a random number following a binomial distribution. Instead of min/max, it takes n (the amount of trials) and p (the probability of success on an individual trial):

{
  "type": "minecraft:binomial",
  "n": 10,
  "p": 0.5
}

You can read this as “flip n coins, each landing heads with probability p, and count the heads.” With n of 10 and p of 0.5 you’ll usually get a number near 5, only rarely 0 or 10. Use uniform when every count is equally likely; use binomial when you want results to cluster around a middle value.

Under the Hood Number providers show up in more places than rolls, and there are more types than these three. There are also score (read a scoreboard value), storage (read a value from command storage), enchantment_level, and sum. You won’t need them for a basic loot table; we’ll meet score and friends when they matter. (Skippable.)

Walkthrough: a weighted treasure chest

Let’s pull it all together into one real file for your pack. Open your mypack pack and create the folder mypack/data/mypack/loot_table/ if it isn’t there yet. Inside it, make a file called treasure_chest.json with this content:

mypack/data/mypack/loot_table/treasure_chest.json

{
  "pools": [
    {
      "rolls": {
        "type": "minecraft:uniform",
        "min": 2,
        "max": 4
      },
      "entries": [
        {
          "type": "minecraft:item",
          "name": "minecraft:bread",
          "weight": 20
        },
        {
          "type": "minecraft:item",
          "name": "minecraft:iron_ingot",
          "weight": 6
        },
        {
          "type": "minecraft:item",
          "name": "minecraft:diamond",
          "weight": 1
        },
        {
          "type": "minecraft:empty",
          "weight": 5
        }
      ]
    }
  ]
}

Walking through it: there’s one pool. Its rolls is a uniform number provider from 2 to 4, so each time the chest is filled the game reaches into the bag 2, 3, or 4 times. Each reach picks one entry by weight. The total weight is 20 + 6 + 1 + 5 = 32, so a single roll gives bread 20⁄32 of the time, an iron ingot 6⁄32, a diamond 1⁄32, and nothing (empty) 5⁄32. A diamond is genuinely rare, but over 2 to 4 rolls per chest you’ll see one now and then.

Save the file and run /reload in your test world to load it. Now you need to point a chest at the table. The cleanest way to test a loot table without setting up structures is the /loot command, which simply runs a loot table and hands you the result. Type this in your chat bar:

/loot give @s loot mypack:treasure_chest

That asks the game to roll your mypack:treasure_chest table and place the results in your own inventory. Run it a few times. You should mostly get bread and iron, with the occasional diamond, and sometimes fewer items than the maximum because of the empty entries and the random rolls.

Figure (to be captured). inventory after running /loot give @s loot mypack:treasure_chest several times, showing mostly bread and iron with one diamond

Under the Hood When a real chest is placed with a loot table attached, the table and a seed are stored on the container as data, and the actual items aren’t generated until there is an interaction with the container (e.g. opening or destroying). That’s why two chests with the same table and seed give identical loot, and why /loot is the quick way to test, since it rolls the table immediately. (Skippable.)

Practice: extend the treasure chest

Now make the table your own. Each of these builds directly on the file above.

  1. Add a tag entry. Give the rare slot more variety: replace the single diamond entry with a tag entry that drops a random arrow from minecraft:arrows, using "expand": true so each roll yields just one arrow type:

    {
      "type": "minecraft:tag",
      "name": "minecraft:arrows",
      "expand": true,
      "weight": 1
    }
    

    (You can use any item tag you learned to write in Chapter 14, including one of your own.)

  2. Add a second pool. Remember the loot table runs every pool. Add a second pool that always gives exactly one “guaranteed” reward, separate from the random bag above.

    {
      "rolls": 1,
      "entries": [
        {
          "type": "minecraft:item",
          "name": "minecraft:emerald"
        }
      ]
    }
    

    Add this as a second element of the top-level pools array (after the first pool’s closing brace, separated by a comma). Now every chest is guaranteed one emerald plus the 2–4 random draws from the first pool.

  3. Try binomial rolls. Change the second pool’s rolls from 1 to a binomial provider with "n": 3 and "p": 0.5. Run /loot give @s loot mypack:treasure_chest a dozen times and watch how the emerald count clusters around 1–2 rather than spreading evenly.

After each change, save and /reload, then test with /loot.

What Can Go Wrong

You used the folder name loot_tables (plural). Modern Minecraft data-pack folders are all singular, so the game looks for loot_table and ignores a loot_tables folder entirely. If /loot give @s loot mypack:treasure_chest says the table is unknown, check the folder name first.

An item entry has no name. An item entry without its name field has nothing to drop and the file won’t load correctly. Every item and tag entry needs a name; only empty (and the number-provider snippets) skip it.

You expected weight to be a percentage. Weight is a share of the total, not a percent. An entry with "weight": 10 isn’t a 10% chance unless the weights happen to total 100. Always compute the chance as weight ÷ total weight of all entries in that pool.

Nothing dropped and you think it’s broken. Remember rolls can be random and you may have an empty entry in the mix, so a single run legitimately producing few or zero items is normal. Run /loot several times before deciding something’s wrong.

Preview: conditions and functions (Chapter 17)

The loot tables in this chapter are pure structure: pools, entries, weights, counts. But you may have noticed two optional fields that keep appearing on pools and entries: conditions and functions. They’re the next two superpowers, and Chapter 17 is devoted to them.

A conditions list is a set of tests that must all pass for a pool or entry to be used (for example, only drop this if the mob was killed by a player). A functions list applies item modifiers onto all item stacks dropped, for example set the dropped count to 3, or add a random enchantment. Here’s a taste (don’t worry about the details yet, this is a Chapter 17 listing):

{
  "type": "minecraft:item",
  "name": "minecraft:diamond",
  "weight": 1,
  "functions": [
    {
      "function": "minecraft:set_count",
      "count": 2
    }
  ]
}

That entry would drop two diamonds instead of one. You’ll learn set_count, enchanting, custom names, and the condition system in the next chapter. For now, you already own the part everything else builds on: you can decide what can drop and how often.

What You Know Now

You can write a loot table from scratch. You know it’s a JSON file in data/<namespace>/loot_table/, that it’s built from pools, that each pool draws one entry per roll, and that weight sets how often each entry wins. You can drop a single item, a whole tag of items, the result of another loot_table, or empty for nothing. And you can make a count exact with constant, random with uniform, or clustered with binomial. You built a weighted treasure chest in mypack and tested it with /loot. Next chapter, you’ll teach those drops to react to who triggered them and to come out modified — conditions and functions.

Chapter 17 — Loot Tables: Conditions and Functions

What You’ll Build

In the last chapter you built a loot table out of pools, entries, and weights: the machinery that decides which items can drop and how rare each one is. That’s half the story. A loot table that can only say “drop a diamond 5% of the time” is useful, but it can’t yet say “…but only if a player did the killing,” or “drop three diamonds,” or “drop a diamond sword that’s already enchanted and has a custom name.” Those two missing powers are what this chapter adds.

The first is conditions, tests that decide when a pool or an entry is allowed to drop at all. The second is functions, steps that modify the item on its way out: bumping its stack size, enchanting it, renaming it, giving it lore. Together they turn a plain list of possible drops into something that reacts to how the loot was earned and hands back exactly the item you designed.

By the end you’ll have made zombies rarely drop a custom-named, enchanted diamond sword, but only when a player lands the kill, by writing one loot table file in the mypack pack you started in Chapter 9. Along the way you’ll learn five condition types and six functions, with the names exactly right.

This chapter extends your mypack pack and uses the test world you’ve used since Chapter 1. It assumes the loot-table structure from Chapter 16 (pools, entries, weights, number providers).

Where conditions and functions live in a loot table

Before meeting any specific condition or function, you need to know where they go. Recall the shape of a loot table from Chapter 16: a root object with a pools list, and each pool with an entries list. Conditions and functions slot into that shape at three levels, and the wiki spells out each one.

At the pool level, a pool may have a conditions field (“a list of predicates, that must all pass for this pool to be used”) and a functions field that “applies item modifiers in order, onto all item stacks dropped by this pool.” At the entry level, a single (singleton) entry may also have its own conditions (“a list of predicates that must all pass for this singleton entry to be included into the pool”) and its own functions, applied “onto all item stacks dropped by this singleton entry.” And at the very top of the file, the loot table’s root object may carry a functions list that applies “onto all item stacks dropped by this table,” covering every pool at once.

So the rule of thumb is: put a condition or function where you want its reach. Gate a whole pool? Use the pool’s conditions. Modify just one item among several? Use that entry’s functions. The syntax is the same everywhere; only the placement changes.

A note on the word “predicate” The wiki calls the things in a conditions list “predicates.” A predicate is just a test that comes out true or false. In this chapter you’ll write these tests inline, right inside the loot table. In Chapter 18 you’ll learn to save a test as its own reusable file (also called a predicate) and point many loot tables, commands, and advancements at it. Same idea, two homes: here it lives inside the loot table; there it gets its own file.

Conditions: deciding when an entry drops

A condition is one entry in a conditions list: a small object that names a test and gives it whatever values it needs. Every condition has a condition field holding the resource location (the namespace:path name you met in Chapter 8) of the test type. If you list several conditions, all of them must pass: the pool or entry drops only when every test comes out true.

Here are the five condition types this chapter uses. The names and fields are copied exactly from the wiki’s predicate page.

random_chance: “Generates a random number between 0.0 and 1.0, and checks if it is less than a specified value.” It has one field, chance, a number from 0.0 to 1.0. So chance: 0.05 means “pass about 5% of the time.” This is how you make a drop rare.

{ "condition": "minecraft:random_chance", "chance": 0.05 }

killed_by_player: “Checks if there is a attacking_player entity.” In plain terms, it passes only when a player dealt the killing blow. If a cactus, a fall, or another mob killed the zombie, this test fails and the gated drop is skipped. It needs no fields at all:

{ "condition": "minecraft:killed_by_player" }

match_tool: “Checks tool used to mine the block.” More broadly, it checks the item that was used (for a mob kill, the weapon in the killer’s hand). It takes a predicate field describing the item to match (the same item-matching format advancements use). You’ll see its shape in the “Try It!” box later; for now, know that this is how a loot table can react to what the player was holding.

entity_properties: “Checks properties of an entity.” It takes an entity field naming which entity from the situation to look at (such as "this", the entity that died) and a predicate field describing what to check about it. This is the general-purpose “look at the mob/player and test something about it” condition.

location_check: “Checks the current location against location criteria.” It takes a predicate field describing the place (biome, dimension, and so on), plus optional offsetX, offsetY, and offsetZ numbers to shift the spot being checked. This is how a drop can depend on where it happened: only in the Nether, only in a particular biome.

Why do killed_by_player and the tool check only work on mob drops? Some conditions need information the situation has to provide. The wiki calls that information loot context: “a set of parameters available to loot tables, predicates, item modifiers, and number providers.” When a living entity dies, its loot context supplies an attacking_player entity and the Tool that was used, which is exactly what killed_by_player and match_tool read. A chest being opened has no killer and no weapon, so those same conditions would always fail there. That’s why this chapter’s project lives on a mob loot table: it’s the context that hands these conditions what they need.

Functions: modifying the item that drops

A function (the wiki also calls it a loot function or item modifier) is one step that changes the item being dropped. Where a condition is a yes/no gate, a function is an action: “set the count to 3,” “enchant it,” “give it this name.” Functions live in a functions list, and each one names its type in a function field, the same resource-location naming as conditions, just a different key. The wiki notes that functions in a list are “applied in order,” so later functions act on the result of earlier ones.

A function can also carry its own conditions list: “A list of predicates, of which all must pass, for this function to be applied.” That lets you, say, only rename the sword when a player got the kill, while still dropping a plain sword otherwise. Handy, but optional.

Here are the six functions this chapter uses, with their exact fields from the wiki.

set_count: “Sets the stack size.” Its main field, count, is a number provider (the exact / uniform / binomial pickers from Chapter 16), so the count can be fixed or randomised. An optional add boolean, when true, makes the change relative to the current count instead of replacing it.

enchant_with_levels: “Enchants the item, with the specified enchantment level (roughly equivalent to using an enchanting table at that level).” Its levels field is a number provider giving the enchantment level to spend; a higher level means stronger, more numerous enchantments, exactly like a real enchanting table. An optional options field is a list limiting which enchantments may be chosen; leave it out and “all enchantments are possible.”

set_name: “Adds or changes the item’s custom name.” Its name field is a text component, the rich-text format from Chapter 5, so you get colour and styling for free. An optional target field chooses which name to set: the wiki’s allowed values are "custom_name" (the default) or "item_name" (the item’s built-in display name).

set_lore: “Adds or changes the item’s lore.” Lore is the small grey description text under an item’s name. Its lore field is a list of text-component lines. It also requires a mode field saying how to combine with any existing lore: one of "append", "insert", "replace_all", or "replace_section". For a fresh item, "replace_all" is the simplest: it sets the lore to exactly your lines.

enchanted_count_increase: “Adjusts the stack size based on the level of the specified enchantment on the killer entity.” This is the Looting bonus: more loot when the killer’s weapon has the Looting enchantment. Its count field (a number provider) is how many extra items to add per level of the enchantment; an optional limit caps the final stack size (0 means no cap); and enchantment names which enchantment to read, normally minecraft:looting.

Modern Minecraft If you follow an older tutorial, you’ll see this function called looting_enchant. That name is gone. In current Java Edition the wiki lists it as enchanted_count_increase: same job (a Looting-style bonus), new name, and it now works for any enchantment you name, not just Looting. If a looting_enchant from an old guide silently does nothing, this rename is why.

set_components: “Sets components of an item.” This is the modern, all-purpose way to attach custom data to an item: its single field, components, is “a map of components ID to component value.” Components are a deep topic. They’re how every piece of modern item data (damage, custom model, container contents, and much more) is stored, so this book gives them their own home in Chapter 21. Here you only need to know set_components exists and is the function you reach for when a simpler one like set_name doesn’t cover what you want.

Modern Minecraft set_components replaces the old set_nbt function you’ll see in pre-1.20.5 tutorials. The mental shift (from one big blob of “NBT” to a tidy map of named components) is the same one running through all of modern item handling, and Chapter 21 is where you’ll learn it properly. For this chapter, the friendly functions (set_name, set_lore, enchant_with_levels) do everything our sword needs, so you won’t have to write a single component yet.

Walkthrough: the rare zombie sword

Time to build the project. The plan: edit the zombie’s loot table so that, on top of its normal drops, a zombie killed by a player has a small chance to drop a diamond sword that is enchanted, named “Cursed Blade,” and carries a line of lore, and gets a Looting bonus to boot.

Step 1 — find the right file

A mob’s drops come from a loot table named after the mob, in the singular. To change what zombies drop, you create a file at this path inside your pack:

mypack/data/minecraft/loot_table/entities/zombie.json

Notice the namespace is minecraft, not mypack. The wiki explains why: “Editing this file in a datapack would make every zombie in a Minecraft world use the modified datapack’s loot table rather than the default zombie loot table.” Because you’re overriding vanilla’s own minecraft:entities/zombie table, your file has to sit at the same address. (This is the same override trick you’d use for any vanilla file: your version wins.)

What Can Go Wrong? Overriding the whole file means you replace vanilla’s zombie drops, not add to them. If you only write your sword pool, zombies stop dropping rotten flesh entirely. The listing below keeps rotten flesh in a first pool so the vanilla drop survives. When you override a vanilla table, you’re responsible for everything it used to do.

Step 2 — write the loot table

Here is the complete file. Read it once top to bottom, then we’ll walk the new parts.

mypack/data/minecraft/loot_table/entities/zombie.json

{
  "type": "minecraft:entity",
  "pools": [
    {
      "rolls": 1,
      "entries": [
        {
          "type": "minecraft:item",
          "name": "minecraft:rotten_flesh",
          "functions": [
            { "function": "minecraft:set_count", "count": { "min": 0, "max": 2 } }
          ]
        }
      ]
    },
    {
      "rolls": 1,
      "conditions": [
        { "condition": "minecraft:killed_by_player" },
        { "condition": "minecraft:random_chance", "chance": 0.05 }
      ],
      "entries": [
        {
          "type": "minecraft:item",
          "name": "minecraft:diamond_sword",
          "functions": [
            {
              "function": "minecraft:enchant_with_levels",
              "levels": { "min": 20, "max": 30 }
            },
            {
              "function": "minecraft:set_name",
              "name": { "text": "Cursed Blade", "color": "dark_purple", "italic": false }
            },
            {
              "function": "minecraft:set_lore",
              "mode": "replace_all",
              "lore": [
                { "text": "Dropped by the restless dead", "color": "gray", "italic": true }
              ]
            },
            {
              "function": "minecraft:enchanted_count_increase",
              "enchantment": "minecraft:looting",
              "count": { "min": 0, "max": 1 }
            }
          ]
        }
      ]
    }
  ]
}

Step 3 — read it back

Start at the top. "type": "minecraft:entity" declares the loot context: this table runs on a mob’s death, which (from the box earlier) is what makes killed_by_player and the Looting bonus work. The first pool is plain Chapter-16 material: one roll, one entry, rotten flesh, with a set_count of 0–2 so the vanilla drop is preserved.

The second pool is where this chapter lives. Its conditions list holds two tests, and both must pass for the pool to run: killed_by_player (a player got the kill) and random_chance with chance: 0.05 (the 1-in-20 rarity). Gate the pool, and the whole sword is skipped most of the time. Clean and cheap.

Inside, the single entry drops a minecraft:diamond_sword, then its functions list reshapes that plain sword in order:

  1. enchant_with_levels with levels 20–30 enchants it as if at an enchanting table set to a random level in that range: strong, varied enchantments every time.
  2. set_name gives it the custom name “Cursed Blade.” The name value is a Chapter-5 text component, so "color": "dark_purple" tints it; the "italic": false is just ordinary text-component styling (Chapter 5) turning the slant off.
  3. set_lore with mode: "replace_all" sets a single grey, italic line of lore beneath the name.
  4. enchanted_count_increase reads minecraft:looting on the killer’s weapon and adds 0–1 extra swords per Looting level, a small bonus for a well-enchanted player.

Step 4 — load and test

Save the file, then in your test world’s chat run /reload so the game re-reads your pack. Now go hit some zombies. Punching them won’t always work. Remember, killed_by_player needs a player kill, and even then the 5% chance means most zombies drop only rotten flesh. Kill a dozen or two and a glowing, purple-named “Cursed Blade” should eventually clatter to the ground.

Figure (to be captured). the “Cursed Blade” diamond sword item dropped on the ground, showing its dark-purple custom name and grey lore line in the floating tooltip

Try It! Want the sword only when the player kills with a particular weapon? Add a match_tool condition to the pool. The wiki says match_tool takes a predicate field that describes the held item, “using same structure as advancements” — the same item-matching format an advancement uses to check what you’re holding:

{
  "condition": "minecraft:match_tool",
  "predicate": { }
}

The exact contents of that item predicate (matching by item id, by item tag, by enchantment, and so on) are the advancement item-condition format. You’ll meet it properly in Chapter 18 alongside predicate files. For now, an empty predicate: {} matches any tool and lets you see the wiring; fill it in once Chapter 18 has shown you the item-predicate fields.

Practice

These extend the zombie-sword table you just built. Make a copy of the file first if you want to keep the original.

  1. Make it rarer, then richer. Lower the random_chance to 0.02. Then add a second line of lore using set_lore. Remember lore is a list, so add another text-component object to the list. Reload and confirm both lines show up.

  2. A location-gated drop. Add a new pool whose conditions include a location_check, and have it drop a small bonus (say, a few minecraft:bone) only somewhere specific. Use the entity_properties or location_check field shapes from this chapter as your guide; you’ll fill in the exact predicate contents properly in Chapter 18, so for now a bare location_check with an empty predicate: {} is enough to see the wiring.

  3. A table-wide function. Move a set_count out of an entry and up to the loot table’s root functions list. Watch how it now applies to every drop (rotten flesh included) because a root function runs “onto all item stacks dropped by this table.” This shows you the difference between gating at the entry, the pool, and the whole table.

What Can Go Wrong

The sword never drops, no matter how many zombies you kill. Two usual causes. First, check you’re killing them yourself: killed_by_player fails for fall damage, cacti, or another mob finishing the job. Second, 5% is genuinely rare; kill more, or temporarily raise chance to 1.0 to confirm the table works, then lower it back.

You changed the file but nothing changed in game. Did you run /reload? Loot-table files hot-reload with /reload (unlike dynamic registries, which need a world reboot, Chapter 7). Also re-check the path: it must be data/minecraft/loot_table/entities/zombie.json exactly, with the singular loot_table folder: the same singular-folder rule that bites people on function and recipe.

Zombies stopped dropping rotten flesh. You overrode vanilla’s whole table and forgot to keep its drops. Add a plain pool for rotten flesh back, as in the listing: overriding means you own all of the mob’s loot, not just your additions.

What You Know Now

You can now make a loot table react. Conditions (random_chance, killed_by_player, match_tool, entity_properties, location_check) decide when a pool or entry is allowed to drop, and you know they read from the situation’s loot context, which is why kill-based and tool-based conditions belong on mob tables. Functions (set_count, enchant_with_levels, set_name, set_lore, enchanted_count_increase, and the all-purpose set_components) decide how the dropped item is modified, applied in order, optionally gated by their own conditions. You override a vanilla mob’s drops by writing a file at the same minecraft: address, and you keep the vanilla loot you still want.

You can now build: rare custom mob drops, enchanted and named reward items, Looting-scaled bonuses, and drops that only happen under conditions you choose. Next, in Chapter 18, you’ll lift those inline conditions out into reusable predicate files, so the same test can guard a loot table, a command, and an advancement without being rewritten each time. And the one function we only previewed here, set_components, opens up in Chapter 21, where data components get the full treatment.

Chapter 18 — Predicates: Reusable Conditions

What You’ll Build

Back in Chapter 17 you wrote conditions on loot pools — little “only if…” tests like random_chance or match_tool that decided whether an entry dropped. Those tests were trapped inside one loot table. This chapter sets them free. You’ll learn that the very same condition vocabulary can be saved on its own, in a small JSON file called a predicate, and then reused anywhere the game accepts a yes/no check: in commands, in target selectors, and back inside loot tables and advancements.

By the end you’ll have added a predicate named mypack:raining_dark_forest to the pack you started in Chapter 9, plus a function that hands out effects to every player standing in a rainy dark forest, and nowhere else. You’ll also finally use execute if predicate and the predicate= selector argument, both of which were named-but-deferred all the way back in Chapters 3 and 4.

This chapter assumes you’re comfortable with /execute and its if/run pieces and chaining (Chapter 4), with target selectors like @a and @e and their square-bracket arguments (Chapter 3), and with the idea of loot-table conditions from Chapter 17, since predicates use the exact same condition types. It also leans on JSON objects, arrays, and booleans (Chapter 8) and the data/<namespace>/... folder layout (Chapters 8–9).

Modern Minecraft Older tutorials wrote the same entity[...]-style checks over and over, copied between commands. Modern data packs write the check once as a predicate and reference it by name. If the rule changes, you edit one file. Predicates are also the only way to express conditions too complex for the selector syntax: “is this entity holding an enchanted diamond sword in a dark forest while it rains” is a predicate, not a string of selector arguments.

What a predicate is

A predicate is a JSON structure the game invokes to check a condition within the world. It returns a pass or fail result to whatever invoked it, which then acts differently based on the result. That’s the whole idea: a predicate is a named yes/no question the game can ask about the world. Put another way, predicates are a flexible way for data packs to encode “if this, then that” logic without needing custom code.

A predicate file is a standalone data pack file containing one or more predicates. Like every other data pack file you’ve made, it lives at a fixed address. Predicate files go in:

data/<namespace>/predicate/

So a predicate you call mypack:is_raining is the file data/mypack/predicate/is_raining.json. The name follows the same namespace:path rule as your functions and recipes.

Inside the file is a JSON object with one required field, condition, and then extra fields that depend on which condition you chose. The root has a condition string (the resource location of the condition type to check) plus the other parts of the predicate specific to that type. The condition value is an identifier just like minecraft:say or minecraft:diamond; for built-in conditions you can write it with or without the minecraft: prefix, and the examples in this chapter spell it out in full so there’s never any doubt.

Here is the smallest useful predicate — “is it raining right now?”:

data/mypack/predicate/is_raining.json

{
  "condition": "minecraft:weather_check",
  "raining": true
}

The condition field picks the kind of check (weather_check), and raining is the extra field that check needs. When the game asks this predicate its question, it answers true (pass) only when it’s raining, and false (fail) otherwise. That’s exactly what weather_check does: it checks the current game weather, with a boolean raining that passes only if it is raining or thundering, and a second boolean thundering that passes only if it is thundering.

Under the Hood (skippable): loot context Some conditions need a fact about the world to do their job: a position, a tool, an entity. This bundle of facts is called the loot context, a set of parameters available to loot tables, predicates, item modifiers, and number providers. Whoever invokes a predicate supplies a context. What happens when a needed fact is missing is clear-cut: a condition like location_check requires an origin provided by loot context, and always fails if it isn’t provided. That’s why some predicates work in one place and silently fail in another. Keep it in mind for “What Can Go Wrong” at the end of the chapter. Conditions marked “invokable from any context” (like weather_check, time_check, random_chance) work everywhere.

Single-condition predicates

Let’s build a small library of one-condition predicates before combining them. Each is a complete file; each uses a standard built-in condition type.

Time of day — time_check

time_check compares the current day time against given values and is invokable from any context. It takes a value (the time to compare against) and an optional period. This predicate passes during the first half of the day:

data/mypack/predicate/is_daytime.json

{
  "condition": "minecraft:time_check",
  "value": {
    "min": 0,
    "max": 12000
  },
  "period": 24000
}

A Minecraft day is 24,000 ticks long (you met that number in Chapter 7). The period field first reduces the time modulo the given number before it’s checked. Setting it to 24000 causes the checked time to be equal to the current daytime, so the comparison resets each day instead of climbing forever. The value here is a min/max range, the long form; there’s also a shorthand where value is a single integer.

A coin flip — random_chance

random_chance generates a random number between 0.0 and 1.0 and checks if it is less than a specified value. Its one field is chance, a success rate as a number from 0.0 to 1.0. This one passes a quarter of the time:

data/mypack/predicate/one_in_four.json

{
  "condition": "minecraft:random_chance",
  "chance": 0.25
}

You met random_chance as a loot condition in Chapter 17. It is the same condition type, and that’s the whole point of predicates. Anything you learned as a loot condition is also a predicate.

Checking an entity — entity_properties

The condition you’ll reach for most often is entity_properties. It checks properties of an entity and is invokable from any context. It has two fields: entity (which entity to look at) and predicate (the actual test, which uses the same structure as advancements). The simplest entity to name is "this", the entity the predicate is being asked about.

This predicate passes when the entity is holding a diamond sword in its main hand:

data/mypack/predicate/holding_diamond_sword.json

{
  "condition": "minecraft:entity_properties",
  "entity": "this",
  "predicate": {
    "equipment": {
      "mainhand": {
        "items": ["minecraft:diamond_sword"]
      }
    }
  }
}

Reading it inside-out: equipment is a field for testing the items that this entity holds in its equipment slots, with one key per slot (the valid keys are mainhand, offhand, head, chest, legs, feet, and body). Each slot holds an item test, and item conditions include an items field, a list of item IDs that tests if the type of item matches any of the listed values. So this whole structure reads: “the item in this entity’s main hand is a diamond sword.”

Checking the block underfoot — block_state_property

block_state_property checks the mined block and its block states, and it requires a block state provided by loot context, always failing if that isn’t provided. It has a block field (a block ID; the test fails if the block doesn’t match) and an optional properties map of block state names to values. This one passes when the block is an oak log laid on its side along the X axis (you met block states with the Debug Stick in Chapter 10):

data/mypack/predicate/sideways_oak_log.json

{
  "condition": "minecraft:block_state_property",
  "block": "minecraft:oak_log",
  "properties": {
    "axis": "x"
  }
}

Because this condition needs a block-state context, it works inside a block’s loot table or a mine context, not from a bare /execute if predicate standing in open air, which supplies no block. We’ll come back to that in “What Can Go Wrong.”

Checking the held tool — match_tool

match_tool checks the tool used to mine the block and, like block_state_property, requires a tool provided by loot context, always failing if it isn’t provided. Its single field is predicate, an item test using the same structure as advancements (the same item test you saw inside equipment above):

data/mypack/predicate/mined_with_netherite_pickaxe.json

{
  "condition": "minecraft:match_tool",
  "predicate": {
    "items": ["minecraft:netherite_pickaxe"]
  }
}

This is the predicate form of the loot condition you used in Chapter 17 to check what tool broke a block. Saved as a file, you can now reuse it across every loot table that wants “only when mined with a netherite pickaxe.”

Checking an active enchantment — enchantment_active_check

The last single condition is a specialist. enchantment_active_check checks if the enchantment has been active, needs the enchantment-active-status context, and is therefore only usable from the enchanted_location loot context. Its one field is a boolean active, whether to check for an active (true) or inactive (false) enchantment:

data/mypack/predicate/enchant_is_active.json

{
  "condition": "minecraft:enchantment_active_check",
  "active": true
}

You won’t call this one from a command. It only makes sense deep inside an enchantment’s own effects, which is a Chapter 35 topic. It’s listed here so you recognize it as a predicate condition and know it belongs to enchantments specifically.

Reference box: the other condition types There are more condition types than this chapter drills. You don’t need them yet, but here’s the full menu so nothing surprises you later: location_check (covered below), damage_source_properties, entity_scores, killed_by_player, survives_explosion, table_bonus, random_chance_with_enchanted_bonus, value_check, and reference (covered at the end of this chapter). Each one is a condition value just like the ones above. Many belong to loot and advancement contexts you’ll meet in Chapters 17, 19, and 36.

Invoking a predicate

A predicate that no one asks is useless. Predicate files can be invoked in several different manners from other data pack files.

From a command, with /execute if predicate. The /execute if predicate subcommand can invoke a predicate file or an in-line predicate definition to decide whether to continue with a subcommand chain. The predicate is invoked once at the current contextual position of execution. It slots into the if/run chain you learned in Chapter 4. For example:

execute if predicate mypack:is_daytime run say Good morning!

(As always in this book, that line lives inside a .mcfunction file, with no leading slash.) The say runs only on the days the predicate passes.

From a target selector, with predicate=. This is the argument that was previewed but deferred back in Chapter 3. The selector argument predicate= checks predicate files as a filter for entity selection. The predicate file is invoked once per entity that needs filtering, each time at the entity’s location. So this selects every player holding a diamond sword, using the predicate file from earlier:

execute as @a[predicate=mypack:holding_diamond_sword] run say You're armed!

Each player is tested in turn; only those who pass are selected. Like every selector argument, you can combine predicate= with the others you know — @a[predicate=mypack:holding_diamond_sword, distance=..10].

From inside loot tables and advancements. Predicates are also used in other locations within other data pack files such as advancements and loot tables. In a loot table, the conditions list you wrote in Chapter 17 is a list of predicates: a pool’s conditions are a list of predicates that must all pass for this pool to be used. So a condition you tested inline in Chapter 17 can instead be saved as a predicate file and pulled in by name, which is the next topic.

Combining conditions: all_of, any_of, inverted

Single checks are handy, but real rules combine them: “raining and in a dark forest,” “diamond sword or netherite sword,” “not daytime.” There are three combiner condition types for exactly this.

all_of — AND. Evaluates a list of predicates and passes if all of them pass. Its field is terms, the list of predicates to evaluate.

any_of — OR. Evaluates a list of predicates and passes if any one of them passes. It also uses a terms list.

inverted — NOT. Inverts another predicate condition. Its field is term (a single predicate, not a list): the condition to be negated.

Each term inside a combiner is itself a full predicate object, the same {"condition": ...} shape, nested as deeply as you like. Here’s “not daytime,” combining inverted with the is_daytime test from earlier, written inline:

data/mypack/predicate/is_nighttime.json

{
  "condition": "minecraft:inverted",
  "term": {
    "condition": "minecraft:time_check",
    "value": {
      "min": 0,
      "max": 12000
    },
    "period": 24000
  }
}

Under the Hood (skippable): the list shortcut A predicate file’s root can be either a compound or a list containing multiple predicates. In the latter case all predicates must evaluate to true. So a JSON array at the top of a predicate file is a built-in all_of. One catch with any_of: any_of only applies to terms; nested lists are all_of. When in doubt, write the combiner out explicitly. It’s clearer to a reader and never surprises you.

Building raining_dark_forest

Now the chapter’s goal: a predicate that passes only when it’s raining and the location is a dark forest. That’s an all_of of two conditions. The first you already have: weather_check with raining: true. The second is location_check.

location_check checks the current location against location criteria, requires the origin context, and has a predicate field whose body uses the same structure as advancements. In that advancement location structure, the biome is tested with a biomes field (the biome at this location), written as a list of biome IDs. The dark forest (the dense, dark-oak biome where woodland mansions generate) has the identifier minecraft:dark_forest, one entry in the full biome ID table Chapter 42 puts to work. Putting it together:

data/mypack/predicate/raining_dark_forest.json

{
  "condition": "minecraft:all_of",
  "terms": [
    {
      "condition": "minecraft:weather_check",
      "raining": true
    },
    {
      "condition": "minecraft:location_check",
      "predicate": {
        "biomes": ["minecraft:dark_forest"]
      }
    }
  ]
}

Reading it top to bottom: the outer all_of passes only when both terms pass. The first term is the weather check. The second is a location check whose inner predicate says “the biome here is in this list,” and the list holds just the dark forest. Because location_check needs the origin position, you must invoke this predicate from somewhere that has a position, which is what /execute ... at ... gives you.

Now a function to use it. We want: for every player, go to where they are, and if the predicate passes there, give them an effect. You built as @a ... at @s chains like this in Chapter 4:

data/mypack/function/dark_forest_effects.mcfunction

execute as @a at @s if predicate mypack:raining_dark_forest run effect give @s minecraft:regeneration 5 0

Walk through it with your Chapter 4 eyes: as @a forks once per player; at @s moves the check to that player’s position (giving location_check its origin); if predicate mypack:raining_dark_forest tests there; and only if it passes does run effect give @s … grant five seconds of Regeneration. Players standing in a rainy dark forest get the effect; everyone else gets nothing.

To make it run continuously, append it to the tick function tag you first created in Chapter 11 and have been adding to since. (You already have entries here from earlier chapters, so add this one to the end of the list, don’t replace them.)

data/minecraft/tags/function/tick.json

{
  "values": [
    "mypack:kill_on_gold",
    "mypack:timer_tick",
    "mypack:dark_forest_effects"
  ]
}

Save, run /reload, fly to a dark forest, and /weather rain. Stand under the dark-oak canopy and watch the Regeneration hearts appear; step into the next biome and they stop renewing.

Figure (to be captured). player standing in a rainy dark forest with the Regeneration effect icon and swirling particles visible; rain falling through the dark-oak canopy

Reusing predicates by name: reference

The last condition type closes the loop on reuse. reference invokes a predicate file and returns its result. Its field is name, the resource location of the predicate to invoke. This lets one predicate stand on the shoulders of another instead of copy-pasting its guts.

Say you want a stricter rule: raining in a dark forest and the player is holding a diamond sword. You already have mypack:raining_dark_forest and mypack:holding_diamond_sword as files, so just reference both:

data/mypack/predicate/armed_in_storm.json

{
  "condition": "minecraft:all_of",
  "terms": [
    {
      "condition": "minecraft:reference",
      "name": "mypack:raining_dark_forest"
    },
    {
      "condition": "minecraft:reference",
      "name": "mypack:holding_diamond_sword"
    }
  ]
}

If you later change what counts as a “dark forest storm,” you edit raining_dark_forest.json once and every predicate that references it updates automatically. One warning: a cyclic reference causes a parsing failure, so don’t make predicate A reference B while B references A.

Practice

  1. Stormy nights only. Write data/mypack/predicate/stormy_night.json as an all_of of two references: mypack:is_nighttime and a new is_thundering predicate (a weather_check with "thundering": true). Add a tick line that gives Night Vision to players when it passes.

  2. Either sword. Write data/mypack/predicate/holding_a_sword.json using any_of with two entity_properties terms, one checking for minecraft:diamond_sword, one for minecraft:netherite_sword in mainhand. Test it with @a[predicate=mypack:holding_a_sword].

  3. Safe zone. Write a predicate that passes when a player is not in a dark forest: wrap a reference to mypack:raining_dark_forest’s location half in an inverted. (Hint: you’ll first want a in_dark_forest predicate holding just the location_check, then invert a reference to it.)

What Can Go Wrong

What Went Wrong? “My predicate never passes anywhere.” You probably invoked a context-hungry condition from a context that doesn’t supply its fact. The rule is explicit: location_check, block_state_property, and match_tool always fail if not provided their origin/block/tool. A bare execute if predicate standing in midair has a position but no block and no tool, so a block_state_property predicate fails there every time. Fixes: give location_check a position with at @s (as we did), and only use block_state_property/match_tool from a loot table or a context that actually breaks a block.

What Went Wrong? “Invalid predicate” / it won’t load. Two classic typos. First, the field that names the check is condition, not type; type is what loot entries and number providers use, but a predicate’s check is keyed by condition. Second, the combiners take their field names exactly: all_of and any_of use a terms list, while inverted uses a single term (no s). Mixing them up (a terms object or an inverted with a list) fails to load.

What Went Wrong? “I nested predicates in a plain list and it acts like AND, not OR.” That’s the list shortcut biting you. A JSON array of predicates means all must pass (all predicates must evaluate to true), and any_of only applies to terms; nested lists are all_of. If you wanted OR, write an explicit any_of with a terms array, not a bare list.

What You Know Now

A predicate is a named yes/no condition stored as JSON in data/<namespace>/predicate/, keyed by a condition field. You can write single checks (weather_check, time_check, random_chance, entity_properties, block_state_property, match_tool, enchantment_active_check) and combine them with all_of, any_of, and inverted. You can invoke a predicate three ways: from a command with execute if predicate, from a selector with predicate=, and from inside loot tables and advancements. You can chain predicates together with reference so a rule is written once and reused everywhere. And mypack now has a raining_dark_forest predicate that fires real effects only when the weather and the biome both agree. Next chapter puts predicates to work as the brains of advancements, the game’s built-in event detectors.

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.

Chapter 20 — Item Modifiers

What You’ll Build

Back in Chapter 17 you learned that a loot table can do more than just pick an item to drop. It can also change that item on the way out, using little building blocks called functions: set_count to change the stack size, set_name to rename it, set_lore to add description lines, enchant_with_levels to enchant it, and so on. Those functions were tucked inside the loot table, only running when the loot table rolled.

In this short chapter you’ll learn how to lift those exact same functions out of a loot table and into their own reusable file, called an item modifier. An item modifier is a recipe for transforming an item, and because it lives in its own file with its own name, you can point at it from anywhere and fire it at any item you like, whenever you like, using a command. No loot table, no mob death, no dropped item required.

By the end you’ll have built a modifier called mypack:upgrade_held that takes whatever item the player is holding and upgrades it on the spot (adding an enchantment and a line of golden lore), plus a one-line function that runs it. You’ll be able to hold a plain stone sword, run the function, and watch it turn into something that looks legendary.

This chapter closes Part V. It extends the mypack pack you started in Chapter 9 and uses the test world you’ve used since Chapter 1.

A function, with a new home

Here is the single most important idea in this chapter, so let’s say it plainly first.

An item modifier is a reusable loot function (or a list of loot functions) saved as its own JSON file. A single modification inside an item modifier is itself called an item function or loot function.

In other words, the things you call “functions” inside a loot table and the things that make up an item modifier are the same things. set_count, set_lore, set_name, enchant_with_levels: every function type you met in Chapter 17 is available here, with the same name and the same fields. The only difference is where the function lives and what makes it run:

  • A loot-table function lives inside a loot table file and runs when that loot table is rolled (a mob dies, a chest generates, you fish something up).
  • An item modifier lives in its own file under data/<namespace>/item_modifier/ and runs when you tell it to, with the /item command, on an item that already exists in the world.

Why bother splitting it out? Two reasons. First, reuse: if five different loot tables all want to “make this a named, enchanted hero item,” you can write that recipe once as an item modifier and have all five point at it, instead of copy-pasting the same block of functions five times. Second, on-demand transformation: a loot table only changes items as they drop. An item modifier can change an item that is already in a chest or in a player’s hand, something a loot table simply can’t do.

Where item modifiers live, and what they look like

Item modifiers are JSON files, and like every other data-pack file you’ve made, their folder is singular and they sit under your namespace:

data/<namespace>/item_modifier/

For your pack that means data/mypack/item_modifier/. The file name (minus the .json) becomes the modifier’s id, exactly like recipes and loot tables: a file at data/mypack/item_modifier/increase_count.json is referred to as mypack:increase_count.

Now the shape. An item modifier is a single loot function, or an array of loot functions, to apply to the item. The root element can be either a single compound following the structure of a loot function, or a list containing multiple loot functions.

So a modifier file comes in two shapes:

  1. A single function: the whole file is one JSON object { ... } describing one function.
  2. An array of functions: the whole file is a JSON list [ ... ] of those objects. When you use the list form, every modifier in it is applied in sequence, one after another, in order.

A single function object always has at least one field:

  • function — the resource location of the loot function type to apply, e.g. "minecraft:set_count". This is the same set of type names you saw in Chapter 17.

It may also carry:

  • conditions — an optional list of predicates (the reusable condition checks from Chapter 18); the function only runs if all of them pass. We won’t need conditions here, but it’s good to know a function can be gated this way.
  • plus whatever extra fields that particular function needs: count for set_count, lore for set_lore, and so on.

Running a modifier with /item modify

A modifier file just sits there until something runs it. The thing that runs it is the /item command. It comes in two variations: item modify invokes a modifier alone upon the target slot, while item replace replaces the item in the target slot with another and then invokes a modifier upon it.

We’ll focus on item modify. Here is its exact syntax:

item modify (block <pos>|entity <targets>) <slot> <modifier>

Reading it left to right: you say whether you’re modifying a block’s container (a chest, a furnace) at a position, or an entity’s inventory (a player or mob); then you give the slot to act on; then the modifier to apply. The <slot> is the name of one inventory slot, and its valid values depend on whether you picked a block or an entity. The slot names we’ll use include:

  • weapon.mainhand — the item in the entity’s main hand (what they’re holding).
  • weapon.offhand — the off-hand item.
  • armor.head — the helmet slot.
  • hotbar.8 — a numbered hotbar slot.
  • container.26 — a numbered slot inside a container block like a chest.

Here is a worked example, which is also the perfect first modifier to build. The file:

data/example/item_modifier/increase_count.json

{
  "function": "minecraft:set_count",
  "count": 1,
  "add": true
}

And the command that fires it at the item in your main hand:

/item modify entity @s weapon.mainhand example:increase_count

That modifier uses the set_count function with "add": true, which means the change is relative to the current count, so each time you run it, the stack in your hand grows by one. Let’s build the same thing in your pack and then make it do something far more interesting.

Make this file:

mypack/data/mypack/item_modifier/increase_count.json

{
  "function": "minecraft:set_count",
  "count": 1,
  "add": true
}

That’s a complete, valid item modifier: a single function, the simplest possible shape. After you save it and run /reload in your test world, hold any stackable item (say, a stack of cobblestone) and type this in the chat bar:

/item modify entity @s weapon.mainhand mypack:increase_count

The stack grows by one. Run it again, and it grows again. You just fired a data-pack file at the item in your hand.

Modern Minecraft — If you follow older tutorials you may see this written with /replaceitem or with raw tag/NBT edits. Those are gone. In current Java Edition the /item command is the way to put items into slots and to transform items in place, and it does it by running an item modifier, the very same loot-function language used everywhere else in the game. Learn this one tool and it pays off in loot tables, advancements, and commands all at once.

Figure (to be captured). a stack of cobblestone in hand growing by one after running mypack:increase_count

Walkthrough: an upgrade-the-held-item modifier

Now for the real goal: a modifier that adds an enchantment and a line of lore to whatever item the player is holding. Because we want to do two things (enchant and add lore), we’ll use the array form of an item modifier: a JSON list of functions, applied in order.

We need two function types from the same loot-function family you met in Chapter 17:

  • set_enchantments — a new enchantment function for us here, and a sibling of the enchant_with_levels you used in Chapter 17. Where enchant_with_levels rolls random enchantments for a given enchanting level, set_enchantments lets you set specific enchantments by name. It modifies the item’s enchantments, and takes an enchantments compound, where each key is an enchantment ID and each value is the enchantment power (level). (The level is technically a number provider, the same little value-or-range objects you met with loot tables, but a plain whole number is a perfectly valid number provider, so we’ll just write 3.)
  • set_lore — the loot function from Chapter 17, used here on its own. It adds or changes the item’s lore, taking a lore list of lines, where each line is a text component (the rich-text format from Chapter 5), plus a required mode field. The allowed modes are "append", "insert", "replace_all", and "replace_section". We’ll use "append" to add our line without erasing any existing lore.

Here is the complete modifier file. It’s a list, so the whole file is wrapped in [ ... ]:

mypack/data/mypack/item_modifier/upgrade_held.json

[
  {
    "function": "minecraft:set_enchantments",
    "enchantments": {
      "minecraft:unbreaking": 3
    }
  },
  {
    "function": "minecraft:set_lore",
    "mode": "append",
    "lore": [
      {
        "text": "Upgraded by mypack",
        "color": "gold",
        "italic": false
      }
    ]
  }
]

Read it as a two-step recipe. Step one (set_enchantments) adds Unbreaking III to the held item: the key minecraft:unbreaking is the enchantment, the value 3 is the level. Step two (set_lore) appends one golden line of lore that reads Upgraded by mypack. The lore line is a plain text component exactly like the ones you wrote in Chapter 5: a text field with color and a styling flag. We set "italic": false because, as you saw with custom text in Chapter 5, Minecraft typically italicizes custom lore, so turning it off keeps the line sitting upright.

Under the Hood (skippable) — Why is the file a list this time, but increase_count.json was a single object? Both are legal. A single object is a one-function modifier; a list lets you chain several. A list behaves much like the sequence modifier type: there’s even an explicit sequence function type that does the same thing. For two functions, the bare list is the simplest form, so that’s what we use.

Now the function that runs it. We point item modify at the player’s main hand:

mypack/data/mypack/function/upgrade_item.mcfunction

item modify entity @s weapon.mainhand mypack:upgrade_held
say Your held item has been upgraded!

Notice there’s no leading / inside the function file. That’s our rule from Chapter 9. The @s selector means “the entity running this function,” so when a player runs it, it acts on their main hand. Save everything, run /reload, then hold a sword (or any item) and run:

/function mypack:upgrade_item

Your held item gains Unbreaking III and a golden Upgraded by mypack line under its name. Hold a different item and run it again, and the same modifier works on whatever you’re holding, because the modifier describes a transformation, not a specific item.

Figure (to be captured). a stone sword tooltip showing “Unbreaking III” and a gold “Upgraded by mypack” lore line after running mypack:upgrade_item

What item modifiers are good for

Now that you can write and fire a modifier, here are the three big uses, the same three the rest of this book leans on.

Applying a set of components. There’s a function type called set_components whose whole job is to set an item’s data components at once. It sets the components of an item, and it can even remove a component by prefixing its name with !. Components are the heart of how modern Minecraft describes items, and they’re the entire subject of the next chapter (Chapter 21), so we’re only previewing here. The point for now: when you want to stamp a whole bundle of properties onto an item in one shot, an item modifier with set_components is the tool, and you’ll meet it properly next.

Transforming loot. Because an item modifier is built from the very same functions a loot table uses, you can write a transformation once and reference it from a loot table with the reference function (which calls sub-functions), instead of repeating the functions inline. Your loot keeps one shared definition of “what a hero item looks like.”

Dynamic item creation. With /item modify (and its sibling /item replace, which can put a fresh item into a slot and then modify it), a data pack can build and alter items while the game is running: upgrade a player’s gear when they hit a milestone, restyle the contents of a chest, swap a tool’s enchantments mid-quest. None of that can a static loot table do on its own.

Practice

  1. A renaming modifier. Write mypack/data/mypack/item_modifier/rename_blade.json as a single function using set_name. set_name adds or changes the item’s custom name and takes a name text component. Give the name a color of your choice. Then make a function that runs item modify entity @s weapon.mainhand mypack:rename_blade and try it on a sword.

  2. Stack the steps. Turn rename_blade.json into the array form and add a second function after the rename: a set_lore with "mode": "append" that adds a flavor line. Run it and confirm both the name and the lore change in one command: proof that a list of functions applies in order.

  3. Fire it at a chest. Place a chest, put an item in its first slot, and from a function run item modify block ~ ~ ~ container.0 mypack:upgrade_held while standing on the chest. (Adjust the ~ ~ ~ coordinates to point at the chest; those relative coordinates are from Chapter 2.) Watch the item inside the chest get upgraded. This is the “transform an item that already exists” power that loot tables don’t have.

What Can Go Wrong

“Unknown item modifier” or the command fails outright. The <modifier> you name must be the resource location of an existing item modifier. Almost always a failure here means a typo in the id or a misplaced file: the path must be exactly data/mypack/item_modifier/upgrade_held.json (folder singular, namespace folder spelled mypack), and you must /reload after creating it. If the id is right but the file won’t load, your JSON is malformed: a missing comma between the two functions in the array is the classic culprit.

Nothing happens, but no error appears. Check the slot. The command fails or does nothing when the target doesn’t have the specified slot. For example, you ran it against an empty weapon.mainhand (you weren’t holding anything), or you aimed at a block that isn’t a container. Hold an item first, and make sure a chest is actually at the coordinates you gave.

The lore line is missing or replaces everything. set_lore requires its mode field. Leave it out and the function is invalid; set it to "replace_all" by accident and you’ll wipe any lore that was already there. Use "append" when you mean “add a line.”

What You Know Now — Part V Recap

This chapter closes Part V, the part where you learned to make Minecraft do things by writing data files rather than by typing commands. You can now:

  • Write recipes, loot tables, predicates, and advancements, and, as of this chapter, item modifiers, which are reusable loot functions in their own files under data/<namespace>/item_modifier/.
  • Choose between the two modifier shapes: a single function object, or an array of functions applied in sequence.
  • Fire a modifier at any item in the world with /item modify, naming the slot (weapon.mainhand, armor.head, container.0, …) to act on.
  • See why an item modifier reaches places a loot table can’t: it transforms items that already exist, on demand.

The single thread running through all of Part V is that the same small building blocks reappear everywhere: a loot function is a loot function whether it’s inside a loot table, referenced from an advancement reward, or standing alone as an item modifier. In Part VI, starting with Chapter 21, we go one level deeper into the items themselves: data components, the named properties that define every item stack, the thing set_components was quietly setting all along.

Chapter 21 — Understanding Data Components

What You’ll Build

Way back in Chapter 1 you learned /give, and you gave yourself plain items: a diamond, a sword, a stack of bread. Since then, little promises have been piling up. In Chapter 5 you saw show_item mentioned but skipped it. In Chapter 17 a loot function called set_components appeared and we said “later.” In Chapter 20 your item modifier used set_components again and we said “later” again.

This is later. This chapter is the keystone of Part VI, and it answers one question: what is an item made of? Once you can answer that, you can build a sword that’s already enchanted, a stick named “Magic Wand,” a bone carrying secret data only your data pack can read, or a glass block you can wear on your head, all from a single /give command, no anvil and no crafting required.

By the end you’ll have a function in your mypack pack called mypack:component_demo that hands you a small collection of custom-built items and, more importantly, you’ll understand the system behind them so the next three chapters are just filling in details. This chapter extends the mypack pack you started in Chapter 9 and uses the test world you’ve used since Chapter 1.

An item is an ID plus components

Here is the big idea, stated as plainly as possible:

Data components, or simply components, are structured data used to store information and define behavior.”

A data component (also called an item component when it lives on an item) is a named property attached to an item. Each component has an ID (a namespace:path identifier, exactly like the ones you met in Chapter 8) and a value. The component minecraft:custom_name holds an item’s name; minecraft:damage holds how worn-out a tool is; minecraft:enchantments holds its enchantments. An item, in modern Minecraft, is really just an item ID plus a bag of components.

That bag can travel: item components can exist anywhere that an item is stored, such as the player’s inventory, container block entities, and structure files. A named sword keeps its name whether it’s in your hand, in a chest, or saved inside a structure file, because the name isn’t painted on. It’s a component riding along with the item.

Modern Minecraft. If you’ve watched older tutorials, you may have heard about NBT tags and things like {display:{Name:...}} glued onto items. Components are the system that replaced most of that. In fact, data components partially replace the NBT format. When a tutorial tells you to edit an item’s NBT directly, it’s almost always out of date: the modern answer is a component. We’ll keep pointing this out, because the internet is full of the old way.

One honest limit: not everything about an item is a component. Some behavior is welded to the item ID itself, and that behavior cannot be removed from the item, nor applied to a different item that does not have that behavior by default. You can’t turn a dirt block into a sword by bolting components onto it. Components decorate and configure an item; they leave what it fundamentally is untouched.

Every item already has components: defaults

You don’t start from an empty bag. Every item type (item ID) has a set of default data components.

A diamond sword doesn’t need you to tell it that it’s a weapon with a durability bar: those facts come built in. Look at the master list of components and you’ll see each one tagged with an item that has it by default: weapon is listed next to the diamond sword, max_damage (durability) next to the diamond axe, food next to cooked beef, enchantment_glint_override next to the experience bottle. Those are default components: the properties an item type carries automatically.

Here’s the clever part. Item stacks must specify an item ID, which implicitly sets these default components, and default components are not saved on individual item stacks. In plain English: the defaults aren’t written down on each individual sword. The game already knows what a diamond sword’s defaults are, so it doesn’t waste space repeating them. A freshly given diamond sword stores nothing extra: it’s just the ID, and the game fills in the rest.

So what do you store? Only the parts you change. Those implicitly-set default components may be overridden by an individual item stack. To override a component is to specify your own value for it on one particular item, replacing the default. That’s the whole game of this chapter: take an item, override a component or two, and you’ve built something custom.

The bracket syntax: [component=value]

Now the syntax you’ve been waiting for since Chapter 1. When you write an item in a command (like the item argument of /give) you can attach components in square brackets right after the item ID. Here is the exact form:

items are represented in the format item_id[component1=value, component2=value], with component being the namespaced ID of a component, and the value being the value of the component written in SNBT format.

Let’s unpack that, because every piece matters:

  • item_id comes first: the item you’re starting from, e.g. diamond_sword. (Because minecraft: is the default namespace from Chapter 8, you may write diamond_sword or minecraft:diamond_sword; they mean the same thing.)
  • [ ... ]: square brackets hold the list of components.
  • component=value: each entry is a component ID, an = sign, then its value.
  • Commas separate multiple components inside the brackets.
  • The value is written in SNBT, the same string-NBT format you learned in Chapter 12, with its {key:value} compounds, [...] lists, and number suffixes. A component value can be a single number, a quoted string, a compound, or a whole text component, depending on the component.

So this command, a standard custom_name example, gives you a renamed stick:

/give @s stick[custom_name={text:"Magic Wand",color:"light_purple",italic:false}]

Read it left to right: start with a stick, override its custom_name component, and the value is a text component: yes, exactly the objects you built in Chapter 5, with text, color, and italic. The named sword from the Cursed Blade in Chapter 17 and the upgraded item in Chapter 20 were setting this very same component, just from inside JSON instead of inside brackets.

Two short rules round this out:

“Any components that are not specified are implicitly set to the component’s default value for that item type. If no components are specified, the square brackets can be removed, leaving just the item ID.”

In other words: anything you don’t mention keeps its default, and diamond with no brackets is just a plain diamond. The brackets are optional; they’re only there when you want to change something.

Removing a component with !

Adding and changing components covers two of the three things you can do. The third is removing one, and it has its own punctuation: the exclamation mark, !. Components can be removed by prefixing them with an exclamation mark, like item_id[!component3].

Notice there’s no =value: you’re knocking the component out rather than setting it to anything. The classic example uses the enchanted-look glow that some items have by default:

/give @s experience_bottle[!enchantment_glint_override]

An experience bottle normally shimmers with the enchantment glint. Writing !enchantment_glint_override removes that override component, taking the shimmer away. (You’ll meet a tidier version of this with a different item in the walkthrough below.) The ! form is how you say “I don’t want this component on this item,” and you’ll reach for it whenever an item’s default behavior is in your way.

Under the Hood (skippable). Remember that some behavior is welded to the item ID and can’t be removed. The ! form removes a component override, pushing a component back toward its default or absent state. It does not let you strip out the hardcoded essence of an item. You can remove enchantment_glint_override from an experience bottle; you cannot remove “is a sword” from a sword.

Where it all lives: the components compound

When you’re typing a /give, you see the friendly bracket form. But once that item is sitting in the world, how is it actually stored? Here is the saved shape of any item:

When saved in the NBT format, items are written as a compound with: id (the item’s resource location), count (how many are stacked, default 1), and components, an optional map of additional (non-default) data components.

So an item on disk is a small compound with up to three tags: id, count, and components. That third one, the components compound, is the bag we’ve been talking about, and the word additional is the key. Only the components you overrode get written there. The defaults stay invisible, filled in by the game, exactly as we said earlier.

The bracket form and the components compound are two views of the same thing. When you write stick[custom_name=...] in a command, the game stores it as a stick whose components compound contains one entry, minecraft:custom_name. The brackets are the keyboard-friendly way to write components, and the components compound is how the game saves them.

Try It! Give yourself a renamed item, then read its components back with the read-only tool from Chapter 10 / Chapter 12: hold the item and run /data get entity @s SelectedItem. You’ll see the components compound printed out, holding only the component you changed: proof that defaults aren’t stored on the stack.

Walkthrough: a function that builds custom items

Let’s put the system to work. You’ll add one function to your mypack pack that gives you a handful of items, each showing off one idea from this chapter. Create this file:

mypack/data/mypack/function/component_demo.mcfunction

say Handing out custom-built items...

# 1. ADD a component: a renamed, recolored stick (text component as the value)
give @s stick[custom_name={text:"Magic Wand",color:"light_purple",italic:false}]

# 2. ADD several at once: a wooden sword that's already enchanted
give @s wooden_sword[enchantments={sharpness:3,knockback:2}]

# 3. CHANGE a number: a diamond axe that's nearly worn out (damage = points used up)
give @s diamond_axe[damage=500]

# 4. REMOVE a component with ! : an experience bottle with no glint
give @s experience_bottle[!enchantment_glint_override]

# 5. Hidden data only your pack can read (we'll use this kind of thing later)
give @s iron_sword[custom_data={foo:1}]

Every line here is a /give whose item carries components in brackets, each a standard worked example of one component. Let’s walk through what each one teaches:

  • The stick adds a custom_name component whose value is a text component. The name shows up in light purple and, because we set italic:false, without the slanted “this-was-renamed” styling.
  • The wooden sword adds two enchantments at once by setting the enchantments component to {sharpness:3,knockback:2}: Sharpness III and Knockback II, ready to swing, no enchanting table needed.
  • The diamond axe changes the damage component to 500. To be precise: damage is the number of durability points used up, so a high number means a nearly-broken tool.
  • The experience bottle removes its enchantment_glint_override with !, so it stops shimmering.
  • The iron sword carries a custom_data component, {foo:1}. The custom_data component holds custom data not used by the game: a private notepad your data pack can stamp on an item and check for later. We’re only planting the flag here; Chapter 24 digs into it.

Now wire the function so you can run it. You already have the mypack:load greeting from Chapter 9; this new function is something you trigger on demand, so you just call it by name. Make sure your pack is loaded, then in the chat bar type:

/reload
/function mypack:component_demo

/reload re-reads your data pack (Chapter 8), and /function runs your new file. Five custom items should land in your inventory. Hover over each one to see the name, the enchantments, and (for the items with hidden data) nothing unusual on the tooltip, because custom_data is invisible to players.

Figure (to be captured). inventory after running mypack:component_demo, showing the purple “Magic Wand” stick, the enchanted wooden sword’s glint, and the non-glinting experience bottle side by side

Modern Minecraft. Notice we never opened an anvil, an enchanting table, or a crafting grid. Every one of these items was born customized, straight from a component. That’s the shift this part of the book is about: in modern Minecraft you describe the item you want in data, and the game makes it.

Practice

These extend the demo function — keep working in the same mypack:component_demo file (or copy it to a new one if you’d like to keep the original).

  1. Two names, one item. A lore component adds description lines under an item’s name. Add a line giving yourself a diamond with both a custom_name and a lore line, separating the two components with a comma inside one set of brackets. (Peek at the next chapter’s territory — that’s fine, you’re just practicing the bracket syntax here.)

  2. Glint on, glint off. enchantment_glint_override can be set, not just removed. Give yourself a plain stick that shimmers by setting enchantment_glint_override to true, then on the next line give yourself an experience_bottle that doesn’t shimmer using the ! removal form. Run both and compare.

  3. Read it back. Give yourself any item with two overridden components, then hold it and run /data get entity @s SelectedItem. Find the components compound in the output and confirm it lists exactly the two components you changed, and none of the defaults.

What Can Go Wrong

You typed : instead of = inside the brackets. Components use component=value, with an equals sign. The format is item_id[component1=value]. A colon belongs inside SNBT compounds ({text:"..."}), not between a component and its value. Mixing them up is the single most common component typo.

You forgot the quotes (or added the wrong ones) in the value. The value is SNBT (Chapter 12), so a text string like a name needs to follow SNBT’s rules: strings inside a component value are quoted, e.g. {text:"Magic Wand"}. If the game rejects your command, re-check that every brace { }, bracket [ ], and quote in the value is balanced and matched, just like any JSON you wrote in Part V.

You expected ! to delete the item’s behavior, and it didn’t. Removing a component with ! only removes an override; it can’t strip behavior that’s welded to the item ID. [!enchantment_glint_override] works because the glint is a component; trying to !-remove a sword’s swordness won’t do anything, because that isn’t a component at all. Such behavior cannot be removed from the item.

What You Know Now

You now understand the modern item system from the ground up. An item is an item ID plus a bag of data components: named properties like custom_name, enchantments, and damage. Every item type ships with default components it fills in automatically and doesn’t bother saving; you build custom items by overriding those defaults. In commands you write components in square brackets after the item ID (item_id[component=value, ...], with values in SNBT), and you remove a component by prefixing it with !, as in item_id[!component]. Under the surface, your overrides live in the item’s components compound alongside its id and count. You can give yourself items with any of this baked in from a single /give. Everything in the next three chapters is just which components exist and what their values look like. Chapter 22 covers how items look (names, lore, rarity, models), Chapter 23 covers what they do (food, tools, weapons, armor, durability), and Chapter 24 covers the specialty components, including a proper tour of the custom_data you just planted.

Chapter 22 — Display Components

What You’ll Build

In this chapter you’ll make a single item look legendary. By the end you’ll have one command that hands you a sword with a glowing purple name, two lines of golden description text underneath it, an “epic” rarity color, and a shimmering enchantment glint, even though the sword isn’t actually enchanted. You’ll write that command into a function in the mypack pack you’ve been building, run it in your test world, and be able to read every piece of it. None of these changes touch what the sword does, only how it looks and reads. That’s the whole idea of this chapter.

Figure (to be captured). the finished “legendary” sword held in hand, tooltip showing the purple name, two gold lore lines, and the enchantment glint

Concepts

Display components vs. functional components

In Chapter 21 you learned that every item carries data components: labelled pieces of data you write inside square brackets after the item’s id, like item_id[component=value]. Some components change what an item does: how much food it restores, what it can break, whether it can be eaten. Others change only how the item looks and reads: its name, the description lines in its tooltip, its color, whether it sparkles. This chapter is about that second group. We’ll call them display components: components whose job is appearance, not behavior. (That’s our grouping name for them; each one is a normal data component from the same list you met in Chapter 21.)

Data components are structured data used to store information and define behavior, and not all characteristics of an item are covered by them. The display components decorate an item without redefining it.

Two names: custom_name and item_name

Here’s a wrinkle that confuses a lot of people: an item can have two different names, and they are separate components.

  • minecraft:item_name is the item’s default base name, written as a text component, present on all items by default. It cannot be erased using an anvil, and it is not italicized. Think of it as the name the item ships with, the one a custom item type would use so it reads as “Ruby” instead of “Diamond” everywhere.
  • minecraft:custom_name is the player-assigned name of this item, block, or entity, typically assigned with an anvil or a name tag. It has the highest priority to display as the item’s name, and appears italic unless overridden by the text component format.

So if an item has both, the custom_name wins on screen. The everyday way to picture it: item_name is the printed label on the box, and custom_name is the sticker you slapped on top.

Modern Minecraft. In older versions, an item’s name was a single NBT field (display.Name). Now there are two separate components with two different jobs. If a tutorial tells you to edit display.Name, it’s out of date. You want custom_name (or item_name) as a component, exactly like the recipes and loot you’ve already been writing.

What a tooltip is made of

A tooltip is the little box that pops up when you hover over an item in your inventory. From top to bottom it can show: the item’s name, then any lore lines you’ve added, then automatic lines the game adds for components that have something to say (enchantments, durability, and so on). The display components in this chapter let you write the name and lore, color the name, and even hide parts of the tooltip you don’t want shown.

Walkthrough: building the legendary sword

We’ll add the components one at a time, see what each does, then combine them into the final command. Every command below goes inside a function file (no leading /), just like every command since Chapter 9. We’ll collect them into one function at the end.

Step 1 — Name it with custom_name

A custom_name value is a text component, the same {text:..., color:..., ...} object you learned in Chapter 5. Here’s an example (it gives a stick, but the shape is what matters):

give @s stick[custom_name={text:"Magic Wand",color:"light_purple",italic:false}]

That gives “a stick named ‘Magic Wand’ in light purple non-italicized text.” Two things to notice. First, the value is a text-component object, so all your Chapter 5 styling fields work here: color, bold, italic, and the rest. Second (and this catches everyone), the example sets italic:false on purpose. A custom_name appears italic unless overridden by the text component format: Minecraft italicizes custom names by default, so adding italic:false turns that off and the name reads upright. We’ll do the same on our sword.

Step 2 — Describe it with lore

Lore is the description text shown below the name in the tooltip. The minecraft:lore component is a list, and each entry in the list is a text component representing one line. The list has a maximum of 256 lines, far more than you’ll ever need.

A one-line example:

give @p stick[lore=[{text:"This Stick is very sticky."}]]

And a two-line example, which is what we want for the sword:

give @p emerald[lore=[{text:"A shiny Emerald!",italic:false,color:"gold"}, {text:"Maybe share it with a friend?",italic:false,color:"yellow"}]]

This gives an emerald that has 2 lines of lore in its tooltip. The first line has a golden color, and the second has a yellow color, and both lines have had their italics removed. Notice the pattern: lore is an array [ ... ], and each element {text:"..."} is one line. To add a line, add another text component to the list.

Try It! Lore lines are full text components, so a line can be obfuscated, colored with a #hex value, or even split into pieces with an extra list (Chapter 5). Try a line that’s {text:"???",obfuscated:true,color:"dark_gray"} for a “cursed, unreadable” effect.

Step 3 — Tier it with rarity

The minecraft:rarity component sets how special the game treats the item, which shows up as the default color of its name. Its value is one of four strings: rarity can be common, uncommon, rare, or epic. If this component does not exist on the item, then common is used.

give @p iron_sword[rarity=epic]

That “gives an iron sword with a light purple name.” So epic paints the name light purple, which is the color we want for something legendary.

Under the Hood (skippable). Rarity sets the default name color, but custom_name carries its own color field, and that wins. On our sword we set both rarity=epic and a purple custom_name color, so they agree. If you ever set rarity=epic but gave the custom_name a different color, the custom_name color would be what you actually see. The rarity color only shows through when the name itself doesn’t specify one.

Step 4 — Make it shine with enchantment_glint_override

The shimmering “enchanted” sparkle on an item is called the glint. Normally only enchanted items have it. The minecraft:enchantment_glint_override component lets you force that decision either way. Its value is a simple boolean (true or false). When true, the item displays a glint, even without enchantments; when false, the item does not display a glint, even with enchantments.

This example removes a glint:

give @s experience_bottle[enchantment_glint_override=false]

That “gives an experience bottle without the visual enchantment glint, which is otherwise applied by default.” For our legendary sword we want the opposite, a glint with no real enchantment, so we’ll use enchantment_glint_override=true.

Step 5 — Assemble the legendary /give

Now we stack all four components into one command, separated by commas inside a single pair of square brackets. Put this in a new function file:

mypack/data/mypack/function/legendary_blade.mcfunction

give @s diamond_sword[custom_name={text:"Stormcaller",color:"light_purple",italic:false},lore=[{text:"Forged in the first storm.",italic:false,color:"gold"}, {text:"Wielded by none who returned.",italic:false,color:"yellow"}],rarity=epic,enchantment_glint_override=true]

Reading it left to right: it gives you a diamond_sword whose custom_name is “Stormcaller” in upright light-purple text; whose lore is two lines, gold then yellow, both upright; whose rarity is epic; and whose glint is forced on. Four display components, one item, no change to how the sword fights.

Wire it into the pack the same way as every function, but this one you’ll trigger by hand rather than on load, so it doesn’t need a tag. In your test world, after /reload, run it from chat:

/function mypack:legendary_blade

You should be holding Stormcaller, glinting and purple, with its two gold-and-yellow lore lines.

Figure (to be captured). chat showing /function mypack:legendary_blade run, and the resulting tooltip

Under the Hood (skippable). You could have typed this whole give straight into chat with a leading /. Putting it in a function instead means you can hand out the exact same legendary blade again and again (from an advancement reward, a loot table, or another function) without retyping a long bracket string. That’s the “think in data packs” habit: build the thing once, call it by name.

The pointer components

The remaining display components don’t carry their own appearance. They point at something else. Three of them point at assets you’ll build later in a resource pack (Chapter 29), so here we’ll learn what they are and the exact value they take, and leave the asset-building for Part VIII.

item_model — swap the item’s whole model

minecraft:item_model replaces what the item looks like by pointing at a model definition. Its value is a string, a resource location. It’s the resource location of the item, which references the item model definition file in a resource pack. This example reuses a vanilla model:

give @s netherite_sword[item_model="minecraft:diamond_sword"]

That “gives a netherite sword that looks like a diamond sword.” When you make your own models in Chapter 29, you’ll point item_model at one of yours (like item_model="mypack:ruby") and the item will render as that model. Be aware that referencing nonexistent models will cause the missing model to be used, so the pointer only works once the asset exists.

custom_model_data — data for a model to read

minecraft:custom_model_data is the classic hook for custom models. Unlike item_model, it doesn’t name a model; it carries data that a model definition reads to decide which variant to show or how to tint it. Its value is a compound holding up to four lists:

  • floats: a list of floats, for the range_dispatch model type.
  • flags: a byte-array of booleans, for the condition model type.
  • strings: a list of strings, for the select model type.
  • colors: a list of RGB values, for the model model type’s tints.

For example:

give @s bone[custom_model_data={floats:[4.0, 5.6, 99.1],strings:["foo:bar"],colors:[8323327, [0.5,0,1], 0x7F00FF]}]

Modern Minecraft. This is a real trap for old tutorials. custom_model_data used to be a single number (custom_model_data:3). It is now a compound with floats, flags, strings, and colors lists, as shown above. If a video tells you to set custom_model_data to one integer, it’s from before the change — use the compound shape shown above. We’ll actually wire it up to a model in Chapter 29; for now, know what its data looks like.

tooltip_style — a custom tooltip box

minecraft:tooltip_style changes the background and frame of the tooltip box itself. Its value is a string resource location pointing at custom sprite textures in a resource pack. It references textures for a _background and a _frame sprite, and invalid specifications will use the missing texture. Like item_model, the pointer is written here but the artwork is a Chapter 29 job.

tooltip_display — hide parts of the tooltip

minecraft:tooltip_display lets you suppress tooltip lines. It allows the tooltips provided specifically by any given item component to be suppressed. Its value is a compound with two fields:

  • hide_tooltip: a boolean. If true, the item has no tooltip when hovered at all.
  • hidden_components: a list of component resource locations; each one’s tooltip line is hidden.

To hide one component’s line while keeping the rest:

give @p diamond_sword[tooltip_display={hidden_components:["minecraft:enchantments"]},enchantments={sharpness:1}]

That “gives a diamond sword that is enchanted with Sharpness I, but doesn’t show the enchantments in the tooltip.” And to hide the whole tooltip:

give @p diamond_sword[tooltip_display={hide_tooltip:1b}]

That gives a sword “that when hovered, it shows no tooltip at all.” (The 1b is the SNBT way of writing the boolean true, from Chapter 12: a b-suffixed 1.)

item_name in practice

We met item_name in the concepts section as the default base name. You set it the same way as custom_name, with a text component. This example uses the plain-string form of a text component:

give @s diamond[minecraft:item_name="Dirt"]

That “gives a diamond that is named ‘Dirt’.” Because a bare string "Dirt" is itself a valid text component (Chapter 5), you can write the name with or without the full {text:...} object. Reach for item_name when you want a base name that isn’t italic and that a player can’t rename away in an anvil, and reach for custom_name (as we did on the sword) for the top-priority, player-style name.

Practice

These extend the legendary blade and the components above. Put each in a function or run it from chat in your test world.

  1. Arm the whole team. Change the give in legendary_blade.mcfunction to target every player instead of just yourself, by swapping @s for @a (Chapter 3). Reload and run it, and everyone online gets a Stormcaller.

  2. A quiet enchantment. Give yourself an enchanted item whose enchantment is hidden from the tooltip and whose glint is turned off, so it looks completely ordinary:

    give @s iron_sword[enchantments={sharpness:2},tooltip_display={hidden_components:["minecraft:enchantments"]},enchantment_glint_override=false]
    

    Confirm in-game that it has no glint and no enchantment line, yet still hits harder.

  3. A renameable label. Give a diamond an item_name of your choice (a base name) and also a custom_name (the sticker on top). Hover it: you should see the custom_name, because it has the highest priority. Then imagine renaming it in an anvil: the custom_name would change, but the item_name underneath would not.

What Can Go Wrong

  • Your custom name comes out italic. You forgot italic:false. A custom_name appears italic unless overridden: Minecraft slants custom names by default. Add italic:false inside the text-component value (as in every example above) to make it upright.

  • rarity does nothing visible. Two common causes. Either you set a custom_name whose own color overrides the rarity color (the name color wins — see the Under the Hood box in Step 3), or you misspelled the value. Rarity must be exactly common, uncommon, rare, or epic; anything else isn’t a valid value.

  • The glint won’t turn off on an enchanted item. Make sure you used enchantment_glint_override=false, not true. false “does not display a glint, even with enchantments”; true forces one on. The two are opposites, and it’s easy to type the wrong one.

What You Know Now

You can change how an item looks and reads without changing what it does. You can name it two different ways, custom_name (top priority, player-style, italic-by-default) and item_name (the un-erasable base name); describe it with lore lines; color its name with rarity; force or remove the enchantment_glint_override shimmer; hide tooltip lines (or the whole tooltip) with tooltip_display; and write the pointers (item_model, tooltip_style, and custom_model_data) that hook an item up to custom artwork you’ll build in Chapter 29. Most of all, you can read a long bracketed /give and say exactly what each component does, the skill the rest of Part VI builds on.

Chapter 23 — Functional Components: Food, Tools, Weapons, and Armor

What You’ll Build

In Chapter 21 you learned what data components are: the named pieces of data stamped onto every item, written in square brackets after the item’s ID like iron_sword[custom_data={foo:1}]. Most of the components you’ve met so far have been about how an item looks or reads: its name, its lore, its custom model. This chapter is about the other half, the components that change how an item behaves.

By the end of this chapter you’ll be able to make almost any item act like almost any other kind of gear. You’ll turn a plain item into food with your own nutrition and a built-in effect; give a tool custom mining rules; turn an item into a weapon, or even into a shield that blocks attacks; make an item wearable in an armor slot; pile stat bonuses onto it; and control its whole durability life: how much it can take, whether it can be enchanted, and what repairs it. You’ll also meet three brand-new components from the 26.x updates: glider (elytra-style flight on any item), kinetic_weapon (charge/ram attacks), and death_protection (a totem’s “save you once” behavior, now data-driven).

To finish, you’ll write one function, mypack:make_items, that hands you three custom items: a snack that grants Night Vision, a pickaxe that rips through stone, and a pair of boots that make you run faster. This chapter extends the mypack pack you started in Chapter 9 and uses the test world from Chapter 1.

Behavior lives in components too

Quick recap of the rule from Chapter 21. In a command like /give, an item is written as item_id[component=value,component=value]: the item’s ID, then its data components listed in square brackets. You can also remove a component an item normally has by putting ! in front of it: item_id[!component]. Anything you don’t list keeps the item’s normal default.

There’s an important line to draw here. Data components cover most of an item’s characteristics, but not all: some behavior is hardwired to the item ID itself and cannot be removed from the item, nor applied to a different item that does not have that behavior by default. So components have limits: you can give a stick a food component and eat it, but you can’t, say, give a stick the exact built-in shooting behavior of a bow just by adding a component. Keep that boundary in the back of your mind; almost everything in this chapter works on almost any item, but a few item behaviors stay fixed.

One more reminder about where we’ll write these commands. Following our rule from Chapter 9, every command goes inside a .mcfunction file with no leading slash, and we run the file with /function. When this chapter shows a command on its own to explain a single component, picture it as one line of such a file.

Food and consuming: build a Night Vision snack

Two components work together to make an item edible: food and consumable.

The food component is a functional component that holds the food stats applied when the item is eaten. It has exactly three fields:

  • nutrition: an integer, the number of food points (the drumsticks on your hunger bar) restored when the player eats it. Must be zero or more.
  • saturation: a number (it can have a decimal), the amount of saturation restored. Saturation is the hidden reserve that keeps your hunger bar from dropping right away.
  • can_always_eat: true or false. If true, the item can be eaten even when your hunger bar is full. Defaults to false.

A simple example is a custom melon slice:

give @s melon_slice[food={nutrition:3,saturation:1,can_always_eat:true}]

That gives “a melon slice that can be eaten at any time and restores 3 food points and 1 saturation.”

On its own, though, food mostly just records stats. To actually control the act of eating (how long it takes, what sound and animation it uses, and what happens the moment you finish) you add the consumable component. If consumable is present, the item can be consumed on use, and if a food component is also present, eating it applies that food’s stats. Its fields:

  • consume_seconds: a number, how many seconds it takes to consume. Defaults to 1.6.
  • animation: which use-animation plays. Must be one of: none, eat, drink, block, bow, spear, crossbow, spyglass, toot_horn, brush, bundle, or trident. Defaults to eat.
  • sound: the sound event played while consuming. Defaults to entity.generic.eat.
  • has_consume_particles: true or false, whether the little munching particles fly out. Defaults to true.
  • on_consume_effects: an optional list of consume effects that fire as a result of eating it.

That last field is where the work happens. A consume effect is one entry describing something that should happen when the item is consumed. The kinds are named in its type field: apply_effects, remove_effects, clear_all_effects, teleport_randomly, and play_sound. For our snack we want apply_effects, which applies status effects to whoever ate it. When the type is apply_effects, these extra fields apply:

  • effects: a list of effect instances. Each one is an object with:
    • id: the ID of the effect, e.g. minecraft:night_vision.
    • amplifier: the strength, where level I is value 0. Optional, defaults to 0.
    • duration: how long, in ticks (20 ticks = 1 second). -1 means infinite. Optional, defaults to 1 tick.
    • plus optional display switches: ambient, show_particles, show_icon.
  • probability: the chance (0.0 to 1.0) the effects are applied. Defaults to 1.0.

Under the Hood (skippable). Here’s a consumable example that clears effects on eat: give @s gold_ingot[consumable={consume_seconds:3.0, animation:'eat', sound:'entity.generic.eat', has_consume_particles:true, on_consume_effects:[{type:'minecraft:clear_all_effects'}]}] This is “a gold ingot that can be eaten in 3 seconds and upon consuming, clears all effects.” Notice the effect’s type there is minecraft:clear_all_effects, which needs no extra fields. We’re using apply_effects instead, which does.

Now our snack. We’ll start from a cookie, make it grant Night Vision for 30 seconds (30 × 20 = 600 ticks) when eaten, and let it be eaten even on a full hunger bar:

give @s cookie[food={nutrition:4,saturation:2,can_always_eat:true},consumable={animation:'eat',on_consume_effects:[{type:'minecraft:apply_effects',effects:[{id:'minecraft:night_vision',duration:600}]}]}]

Eat it and your screen brightens for half a minute. The food component handles the 4 food points and 2 saturation; the consumable component’s on_consume_effects handles the Night Vision.

Modern Minecraft. In older tutorials you’ll see people fake custom food with command blocks that watch for an item being eaten, or with hardcoded item NBT. In current Minecraft the food is the data: the food and consumable components describe the whole behavior, and the game does the rest.

Figure (to be captured). a cookie tooltip in hand, and the same player eating it with the Night Vision screen brightening

Tools: a pickaxe that eats through stone

The tool component marks an item as a tool and spells out how it mines. Its fields:

  • default_mining_speed: a number, the mining speed used when no rule below overrides it. Defaults to 1.0. (A plain hand is 1.0; higher is faster.)
  • damage_per_block: an integer, how much durability is removed each time you break a block with it. Defaults to 1.
  • can_destroy_blocks_in_creative: true/false, whether you can break blocks holding it in Creative. Defaults to true.
  • rules: a list of special-case rules. The game reads them in order and the first matching rule wins. Each rule is an object with:
    • blocks: which blocks it applies to: a single block ID, a block tag written with a # (like #minecraft:mineable/pickaxe, meaning “every block a pickaxe is meant to mine”), or a list of block IDs.
    • speed: if the blocks match, the mining speed to use instead of the default. Optional.
    • correct_for_drops: if the blocks match, whether this tool counts as the correct tool: mining at full speed and actually dropping the block’s items. Optional, defaults to false.

Here’s an example that turns a humble fence into a pickaxe:

give @p oak_fence[max_stack_size=1,max_damage=350,damage=0,tool={default_mining_speed:1.5,damage_per_block:2,rules:[{blocks:"#mineable/pickaxe",speed:6,correct_for_drops:true}]}]

That “gives an oak fence that has the properties of a pickaxe”: speed 6 on anything in the pickaxe tag, and it drops what it mines. Notice it pairs tool with max_damage, damage, and max_stack_size so the fence has durability and stops stacking, just like a real tool. We’ll come back to those durability components at the end of the chapter.

For our practice item we’ll take a real diamond pickaxe and make it shred stone specifically, very fast:

give @s diamond_pickaxe[tool={default_mining_speed:1.0,rules:[{blocks:'minecraft:stone',speed:25,correct_for_drops:true}]}]

A rule with blocks:'minecraft:stone' and speed:25 means: when you hit stone, mine at speed 25 (far faster than a normal pickaxe), and because correct_for_drops is true, the stone still drops properly. Every other block falls back to default_mining_speed.

Weapons, reach, and shields

Now combat. This is where lots of outdated tutorials get it wrong, so read carefully. The real weapon component differs from what the old guides claim.

weapon — the fields that really exist

Here is the single most important correction in this chapter. The weapon component does not hold “attack damage” or “attack speed.” If present, the item is a weapon, but for attack damage you use the attribute_modifiers component. The weapon component itself has exactly two fields:

  • item_damage_per_attack: an integer, how much durability the item loses per attack. Defaults to 1.
  • disable_blocking_for_seconds: a number, how many seconds this weapon can disable a blocking shield when it lands a hit. If 0, it can’t disable shields. Defaults to 0.

For example:

give @p iron_sword[weapon={disable_blocking_for_seconds:5,item_damage_per_attack:10}]

This is “an iron sword that disables shields for 5 seconds when used on them, but loses 10 durability for each attack performed.” So weapon is about durability cost per swing and shield-breaking, not the damage number. Attack damage comes from attribute_modifiers, which we build later in this chapter.

Modern Minecraft. If a tutorial tells you to put attack_damage or attack_speed inside the weapon component, it’s describing a version that no longer matches the game. In current Minecraft, those numbers are attributes (next section on attribute_modifiers), and weapon only carries item_damage_per_attack and disable_blocking_for_seconds.

attack_range — how far your hit reaches

The attack_range component sets the melee reach of a weapon: how far the target can be and still count as hit. Its fields (all distances in blocks):

  • min_reach: minimum distance to count as a valid hit. Defaults to 0.0.
  • max_reach: maximum reach in Survival. Defaults to 3.0.
  • min_creative_reach: minimum in Creative mode. Defaults to 0.0.
  • max_creative_reach: maximum in Creative mode. Defaults to 5.0.
  • hitbox_margin: extra margin added to the target’s box when checking the hit. Defaults to 0.3.
  • mob_factor: a multiplier on the reach when a mob (not a player) uses the item. Defaults to 1.0.

So a long spear-like reach of 5 blocks is just:

give @s diamond_sword[attack_range={max_reach:5.0}]

blocks_attacks — turn any item into a shield

This is one of the headline new abilities: the blocks_attacks component lets any item be used like a shield. When present, the item can be used like a shield to block attacks to the holding player. Its fields:

  • block_delay_seconds: how long you must hold use before blocking kicks in. Defaults to 0.
  • disable_cooldown_scale: a multiplier on how long the item gets disabled when hit by a shield-disabling attack (that’s the disable_blocking_for_seconds from the attacker’s weapon component). If 0, this item can never be disabled. Defaults to 1.
  • damage_reductions: a list of rules for what and how much damage to block. Each rule is an object with:
    • type: a list of damage types to block (each written as a damage type id like mob_attack, or a #-prefixed damage type tag). Optional; defaults to all damage types.
    • base: a flat amount of damage to block. Required.
    • factor: the fraction of incoming damage to block (0.0 to 1.0). Required.
    • horizontal_blocking_angle: the widest angle (in degrees) between where you’re facing and the incoming attack that can still be blocked. Defaults to 90.
  • item_damage: an object controlling how much the item is damaged when it blocks:
    • threshold: minimum incoming damage before the item takes any. Defaults to 0.
    • base: flat item damage once the threshold is passed. Defaults to 0.
    • factor: fraction of the blocked damage applied to the item. Defaults to 1.5.
  • block_sound: sound event when an attack is successfully blocked. Optional.
  • disabled_sound: sound event when the item goes on its disabled cooldown. Optional.
  • bypassed_by: a damage type tag (#...) listing damage types that ignore the block entirely. Optional.

Coming in Chapter 36. Two of these fields (damage_reductions[].type and bypassed_by) name damage types: individually (like mob_attack), or as #-prefixed tags standing for a whole group (“all fire damage,” “all explosions”). Tags and damage types are a topic of their own; we cover them fully in Chapter 36. For now just know you can leave both fields off to block (or not bypass) everything.

Here’s an example that makes a sword block half of certain damage:

give @s diamond_sword[blocks_attacks={disable_cooldown_scale:0,damage_reductions:[{type:[mob_attack,arrow,explosion],base:0,factor:0.5}],block_sound:block.anvil.place}]

This is a diamond sword that blocks half the damage from mob attacks, arrows, and explosions, can’t be disabled (disable_cooldown_scale:0), and clangs like an anvil when it blocks.

Three new 26.x components: glide, charge, and cheat death

These three components are new in the 26.x updates and are worth meeting on their own.

glider — elytra wings on anything

The glider component, when present, allows living entities to glide (as with elytra) when equipped. It has no fields of its own: its value is just an empty object {}. The catch is that gliding only works while the item is equipped, so you pair it with the equippable component (coming up next). One detail worth knowing: if the glider item is damageable, it only works while its damage is below max_damage − 1, and every second of gliding tries to wear off one point of durability.

Here’s an example that makes a nether star into head-slot wings:

give @s nether_star[equippable={slot:"head"},glider={}]

This is “a nether star that can be equipped in the head slot, and if placed on the head, it allows the player to glide.”

kinetic_weapon — charge and ram attacks

The kinetic_weapon component enables a charge-type attack: while you’re using the item, the damage is dealt based on how fast you and your target are moving toward each other. The game’s own example item is the Copper Spear. Its fields:

  • delay_ticks: ticks of wind-up before the weapon becomes effective. Defaults to 0.
  • forward_movement: how far the item lunges out of your hand during the animation. Defaults to 0.0.
  • damage_multiplier: multiplier turning relative speed into damage. Defaults to 1.0.
  • damage_conditions: an object describing when the charge deals damage.
  • knockback_conditions: an object describing when it knocks the target back.
  • dismount_conditions: an object describing when it knocks a rider off their mount.
  • sound: optional sound event when the weapon is engaged.
  • hit_sound: optional sound event when it hits an entity.

Each of those three *_conditions objects shares the same shape:

  • max_duration_ticks: how long (in ticks, counted after the delay) the condition keeps being checked.
  • min_speed: minimum speed of the attacker (blocks per second, along where they’re looking). Optional, defaults to 0.0.
  • min_relative_speed: minimum relative speed between attacker and target. Optional, defaults to 0.0.

Here’s an example that builds a charge weapon out of an amethyst shard:

give @s amethyst_shard[kinetic_weapon={forward_movement:0.0,delay_ticks:20,damage_conditions:{max_duration_ticks:60},knockback_conditions:{max_duration_ticks:40},dismount_conditions:{max_duration_ticks:20},hit_sound:"block.amethyst_cluster.step"}]

This is a charge attack that arms after a 1-second delay (20 ticks), then can damage for 3 seconds, knock back for the first 2, and dismount for the first 1.

death_protection — a data-driven totem

The death_protection component is the totem-of-undying behavior, now something you can stamp on any item. If present, the item protects the holder from dying by restoring a single health point. It has one optional field:

  • death_effects: a list of consume effects (the same kind you used for the food) that fire when the item saves you.

Here’s an example that puts it on a nether star:

give @s nether_star[death_protection={death_effects:[{type:'minecraft:clear_all_effects'}]}]

This is “a nether star that protects the holder from death and removes all status effects from the holder.”

Wearable gear and stat bonuses: build the speed boots

equippable — make an item wearable

The equippable component lets an item be worn in an equipment slot. It has a lot of fields; the ones you’ll use most are:

  • slot: which slot it goes in. One of: head, chest, legs, feet, body, mainhand, offhand, or saddle.
  • equip_sound: sound event when you put it on. Defaults to item.armor.equip_generic.
  • asset_id: the resource location of an equipment model to draw when it’s worn (this points at a file under assets/<namespace>/equipment/<id>.json). If you leave it out, the item renders as itself (or, off the head slot, may not render at all).
  • allowed_entities: limits which entities can wear it. Defaults to all.
  • dispensable: whether a dispenser can equip it. Defaults to true.
  • swappable: whether right-clicking equips it into its slot. Defaults to true.
  • damage_on_hurt: whether it loses durability when the wearer is hurt. Defaults to true.
  • equip_on_interact: whether you can equip it onto a mob by pressing use on the mob. Defaults to false.

(There are also camera_overlay, can_be_sheared, and shearing_sound fields for special cases.)

Coming in Part VIII. The asset_id field points at a custom equipment model, which is a resource pack file — art, not behavior. We name it here so you recognize it, but building the model is a job for the resource-pack chapters in Part VIII. Our boots will simply behave like boots without custom art.

A first example equips a glass block on the head:

give @s glass[equippable={slot:"head",equip_sound:"block.glass.break",dispensable:true}]

attribute_modifiers — stacking stat bonuses

To make our boots actually do something, we need attribute_modifiers. Remember from the glossary that an attribute is a numeric property of a player or mob (like max health, movement speed, or attack damage) with a base value plus modifiers. The attribute_modifiers component is a list of modifiers the item applies while it’s equipped in the right slot. One rule is worth keeping in mind: if the item is not in the correct equipment slot, it has no effect, and the modifiers are removed the moment the item leaves that slot.

Each modifier in the list is an object with these fields:

  • id: a namespaced ID naming this modifier. It must be unique among modifiers of the same attribute (so two boots can’t both use the same id on the same stat).
  • type: the namespaced ID of the attribute to act on (this is which stat: movement speed, attack damage, and so on).
  • slot: which equipment slot the item must be in for the bonus to count: one of any, hand, armor, mainhand, offhand, head, chest, legs, feet, body, or saddle. Defaults to any (meaning any equipment slot, not any inventory slot).
  • operation: how the number is applied. One of:
    • add_value: add the amount straight to the stat.
    • add_multiplied_base: add a fraction of the base value.
    • add_multiplied_total: multiply the running total.
  • amount: the number used by the operation.
  • display: optional; controls how the bonus appears in the tooltip.

Here’s an example that grows the player with a scale modifier:

give @s stick[attribute_modifiers=[{type:"minecraft:scale",slot:"hand",id:"example:grow",amount:4,operation:"add_multiplied_base"}]]

This is “a stick that causes the player to grow 4x when holding it.” Notice the shape: a list, each entry with type (the attribute), slot, id, amount, and operation. We’ll follow that exact shape for our boots, but target movement speed instead of scale, and the feet slot instead of hand.

A note on attribute names. The example above uses the attribute minecraft:scale. The attribute for run speed is named minecraft:movement_speed and the one for melee damage is minecraft:attack_damage. These are the standard attribute IDs the attribute_modifiers component expects in its type field. The full catalogue of attribute IDs lives with the Attribute system; when you need a stat that isn’t one of these, the wiki’s Attribute page lists every attribute ID by name, along with the exact value range each one accepts.

Now the boots. We take diamond boots and add a movement-speed bonus that only counts while they’re on your feet:

give @s diamond_boots[attribute_modifiers=[{type:"minecraft:movement_speed",slot:"feet",id:"mypack:speed_boost",amount:0.1,operation:"add_multiplied_base"}]]

The operation:"add_multiplied_base" with amount:0.1 means “add 10% of your base walking speed,” and slot:"feet" means the bonus only applies while the boots are actually worn. Drop them in a chest and you slow back down.

The durability lifecycle

The last family of components controls an item’s durability: how much wear it can take, whether it can be enchanted, and what fixes it. You met several of these in passing already (the fence-pickaxe used max_damage and damage); here they are in full.

max_damage is an integer: the maximum damage an item can take before breaking, in other words its total durability. If it isn’t set, the item can’t take damage at all. It must be a positive non-zero integer, and it can’t be combined with a max_stack_size greater than 1 (a tool that wears out can’t stack). For example:

give @s diamond_pickaxe[max_damage=4]

This is “a diamond pickaxe that can only be used 4 times before breaking.”

damage is an integer: the number of uses already consumed, not the amount remaining. A fresh item is 0. The durability bar only appears when both damage and max_damage are present, which is why the fence example earlier set damage=0 explicitly.

give @s diamond_axe[damage=500]

This is “a diamond axe with 500 points of damage” (that is, 500 uses already spent).

unbreakable, when present, makes the item never lose durability: the durability bar disappears and a blue “Unbreakable” line is added to the tooltip. Its value is an empty object:

give @p wooden_spear[unbreakable={}]

enchantable decides whether the enchanting table works on the item. It has one field, value, a positive integer for the item’s enchantability: a higher number lets stronger enchantments be offered. For example:

give @s elytra[enchantable={value:15}]

This is “a pair of elytra that can be enchanted in an enchanting table with an enchantability of 15.” (Writing the enchantments themselves, the enchantments component and custom enchantment files, is a job for Chapters 24 and 35.)

repair_cost is an integer: the number of experience levels added to the base cost when you repair, combine, or rename the item in an anvil. A fresh item is 0; the number climbs each time you work the item, which is why heavily-used gear gets “too expensive.”

give @s diamond_sword[repair_cost=5]

repairable says what materials can repair the item in an anvil. Its one field, items, is a single item ID, a list of item IDs, or a #-prefixed item tag. For example:

give @p diamond_sword[repairable={items:"stick"}]

This is “a diamond sword that can be repaired with sticks in an anvil.”

use_cooldown sets a cooldown after the item is used (like the ender pearl’s). Its fields:

  • seconds: the cooldown length in seconds.
  • cooldown_group: an optional resource location. If set, the item shares its cooldown with every other item in the same group instead of just other items of its own type.

For example:

give @p ender_pearl[use_cooldown={seconds:10,cooldown_group:"foo:bar"}]

This is a 10-second cooldown that also applies to any item sharing the foo:bar group.

Putting the three practice items in one function

Let’s collect the three practice items into a single function you can run whenever you want them. Following the rule from Chapter 9, there’s no leading slash inside the file:

mypack/data/mypack/function/make_items.mcfunction

give @s cookie[food={nutrition:4,saturation:2,can_always_eat:true},consumable={animation:'eat',on_consume_effects:[{type:'minecraft:apply_effects',effects:[{id:'minecraft:night_vision',duration:600}]}]}]
give @s diamond_pickaxe[tool={default_mining_speed:1.0,rules:[{blocks:'minecraft:stone',speed:25,correct_for_drops:true}]}]
give @s diamond_boots[attribute_modifiers=[{type:"minecraft:movement_speed",slot:"feet",id:"mypack:speed_boost",amount:0.1,operation:"add_multiplied_base"}]]
say You received a Night Vision snack, a stone-breaker pickaxe, and speed boots!

Save it, run /reload, then run:

/function mypack:make_items

You’ll get all three items at once. Eat the cookie to see your screen brighten; mine stone with the pickaxe to feel it tear through; put the boots on to run faster, and take them off to confirm the speed bonus vanishes with them.

Figure (to be captured). a player inventory holding the three custom items, with the boots equipped and the Night Vision effect active

Practice

  1. A super-snack. Make a custom food on a different item (say apple or bread) that grants two effects at once (for example Night Vision and Speed) by putting two objects in the effects list inside one apply_effects consume effect. Make it can_always_eat:true.

  2. A faster wood pickaxe. Build a tool on a wooden_pickaxe whose rules give it a high speed on the block tag #minecraft:mineable/pickaxe (every block a pickaxe normally mines), with correct_for_drops:true. Compare how it feels against your stone-only pickaxe from the chapter.

  3. A glide hat. Recreate the glider example on a different head item, combining equippable={slot:"head"} with glider={}. Equip it and try gliding off a cliff. (Remember: glider has no fields, its value is just {}.)

  4. Real armor stats. Add a second modifier to your speed boots so they also grant a little extra max health. Put a second object in the attribute_modifiers list with its own unique id, the max-health attribute in type, slot:"feet", and operation:"add_value". (Look up the attribute ID for max health (and the maximum value it accepts) on the wiki’s Attribute page; see the note above.)

What Can Go Wrong

What Went Wrong? My food item won’t let me eat it. You probably added food but forgot consumable. The food component holds the stats, but it’s the consumable component that actually makes the item consumable on use. Add a consumable={...} (even an empty set of options works for plain eating) alongside your food, and the effects will fire.

What Went Wrong? I put attack_damage inside the weapon component and the game rejected it (or ignored it). That field doesn’t exist on weapon. The weapon component only has item_damage_per_attack and disable_blocking_for_seconds, and attack damage comes from the attribute_modifiers component. Move your damage number into an attribute_modifiers entry whose type is the attack-damage attribute.

What Went Wrong? My boots’ speed bonus shows in the tooltip but doesn’t kick in, or works in any slot. Check the slot field on the modifier. Attribute modifiers only take effect when the item is in the correct equipment slot; if you leave slot off it defaults to any equipment slot, and if you set the wrong slot the bonus simply never applies. For boots, the slot is feet.

Chapter 24 — Specialty Components

What You’ll Build

Over the last three chapters you’ve been learning data components, the named properties that ride along on an item stack. Chapter 21 taught you the system: every item has default components, and you override them inside square brackets with the /give command, like diamond_sword[custom_name='"My Sword"']. Chapter 22 covered the components that change how an item looks (name, lore, rarity, glint), and Chapter 23 covered the ones that change what an item does (food, tools, weapons, armor).

This chapter is the grab-bag that closes Part VI. It’s a guided tour of the rest of the components worth knowing: the ones that let an item carry enchantments, hold a potion’s effects, store other items inside itself, draw a custom banner or map, trim a piece of armor, place a pre-loaded block, or carry a secret note that only your data pack reads. None of these is hard on its own. The skill this chapter builds is recognising which component does which job, and reading its value shape so you can write it correctly.

By the end you’ll have made a custom splash potion that throws a cloud of your own chosen effects, using nothing but a /give command. You’ll also have met custom_data again (the component that is your data pack’s private scratchpad) and seen why it’s the single most important component in this whole chapter for the kind of work the rest of the book does.

This chapter extends the mypack pack from Chapter 9 and uses the test world from Chapter 1. Everything here is a /give command you can type straight into the chat bar to see the result instantly.

How to read this chapter. This is a reference tour, not a single build. Skim the headings, try the /give examples that interest you, and come back to look things up later. Every component name and value shown is copied exactly from the wiki’s Data component format page. When you need the precise spelling of a field, this chapter (and Appendix C) is where to find it.

A quick reminder of the shape

From Chapter 21, the format inside the square brackets is always the same:

item_id[component1=value, component2=value]

Each component is a name, and each value is written in SNBT, the same text form of NBT you learned in Chapter 12 (numbers, quoted strings, {} compounds, [] lists, the 1b/0b booleans). A few components are a single word or number; most are a {...} compound or a [...] list. The wiki’s Data component format page says it plainly:

“items are represented in the format item_id[component1=value,component2=value], with component being the namespaced ID of a component, and the value being the value of the component written in SNBT format.”

You can leave the minecraft: namespace off a component name in a command (the game assumes it), so enchantments and minecraft:enchantments mean the same thing. We’ll write the short form throughout.

Enchantments: enchantments and stored_enchantments

The enchantments component holds a map of each enchantment to its level: a list of key-value pairs where the key is the enchantment’s resource location and the value is the level number. Put it on a sword and the sword is actually enchanted:

/give @s wooden_sword[enchantments={sharpness:3,knockback:2}]

That gives a wooden sword with Sharpness III and Knockback II. (Notice the level is the roman-numeral tier, so 3 means III.)

There’s a near-twin called stored_enchantments, and the difference matters. The wiki spells it out:

“This component adds active enchantments and should not be confused with the stored_enchantments component, which is used to add inactive enchantments, such as with enchanted books.”

So enchantments makes the enchantment work right now: swing the sword and Knockback fires. stored_enchantments is what an enchanted book uses: the enchantment is dormant, just carried, until you combine the book with a real item in an anvil. The clearest possible test:

“hitting an entity with an enchanted_book[enchantments={knockback:2}] would knock any entity hit per knockback II while hitting an entity with an enchanted_book[stored_enchantments={knockback:2}] would not.”

Both use the same inner shape ({enchantment_id: level}); they differ only in whether the enchantment is live or stored.

Modern Minecraft. In old tutorials you’ll see enchantments written as a long Enchantments:[{id:...,lvl:...}] NBT list. The modern component is the compact map you see above: one enchantment per line, name then level. If a tutorial shows the old list form, it’s pre-component and won’t paste in as-is.

Potions: potion_contents

The potion_contents component is what makes a potion a potion. It holds three things:

“The base potion, custom list of mob effects, and custom color contained in this potion, splash potion, lingering potion, tipped arrow, or area effect cloud.”

The simplest form just names a base potion:

/give @a potion[potion_contents={potion:"minecraft:night_vision"}]

That’s a plain Night Vision potion. But you can skip the base potion entirely and supply your own list of effects with custom_effects. Each effect is a compound with an id (which effect), an amplifier (the level, where 0 is level I), and a duration in ticks. Here is an example of a potion carrying a custom Wither effect:

/give @a potion[potion_contents={custom_effects:[{id:"minecraft:wither",amplifier:1,duration:3600}]}]

amplifier:1 means Wither II, and duration:3600 is 3600 ticks. From Chapter 7 you know there are 20 ticks per second, so that’s 180 seconds, three minutes. We’ll build on this exact shape in the practice at the end.

There’s a companion component, potion_duration_scale, that multiplies how long the effects last:

/give @p potion[potion_contents={potion:swiftness},potion_duration_scale=2]

That gives a Potion of Swiftness whose default 3-minute duration is doubled to 6 minutes: the =2 is the multiplier, so =3 would triple it.

Items that hold items: container and bundle_contents

Some items carry other items inside them. A shulker box is the classic example, and the container component is how its contents are stored. It holds the items contained in the container’s slots, and each entry pairs an item with a slot number:

/give @s barrel[container=[{slot:0,item:{id:apple}}]]

That’s a barrel with an apple already sitting in its first slot (slot 0). Each entry in the list is a compound with two parts: item (the item stack to store) and slot (which slot, numbered from 0). The component supports up to 256 slots, though a given block only uses as many as it has: a chest uses 27, a decorated pot just 1.

A bundle holds items too, but more loosely: it has no fixed slots, just a pile. Its component is bundle_contents, and it’s simply a list of item stacks:

/give @s bundle[bundle_contents=[{id:"diamond",count:2}]]

That bundle starts with two diamonds in it. Note that bundle_contents only does anything on an actual bundle; adding this component to any item other than a bundle does nothing.

Fireworks: firework_explosion and fireworks

These two go together. A firework star carries a single firework_explosion, one burst effect. A firework rocket carries fireworks, which bundles a list of those bursts plus a flight duration.

A single explosion (a firework star) has these fields:

  • shape: the burst shape, one of small_ball, large_ball, star, creeper, or burst.
  • colors: a list of colors (as packed integers) for the initial particles.
  • fade_colors: a list of colors the particles fade into.
  • has_trail: a boolean; whether the burst leaves a trail (the diamond effect).
  • has_twinkle: a boolean; whether it twinkles (the glowstone-dust effect).

The fireworks component (the rocket) wraps that up:

  • flight_duration: a byte from -128 to 127 (defaults to 1); this is also how many gunpowder the rocket would take to craft.
  • explosions: a list of explosion compounds, each with the same shape/colors/ fade_colors/has_trail/has_twinkle fields as above (up to 256 of them).

A note on colors. Firework colors are stored as a color packed into a single integer: one number that encodes red, green, and blue together. We’ll meet exactly how that number is built in a moment with map_color. For now, know that a firework color is one integer per color, listed inside colors or fade_colors.

Try It! Build a firework /give line of your own from the field list above: try a firework star with firework_explosion={shape:"star",has_twinkle:true} and watch the shape in the tooltip. Adding colors requires a packed-integer color; see map_color below for how to read one.

Compasses: lodestone_tracker

A lodestone compass points at a fixed spot instead of spinning toward spawn. The lodestone_tracker component stores where it points:

  • target: an optional compound holding pos (the block coordinates as an integer array) and dimension (the dimension’s ID). If target is left out, the compass spins randomly.
  • tracked: a boolean. If true (the default), the component is removed when the lodestone is broken; if false, the compass keeps pointing there even with no lodestone.

To make a compass that points toward a lodestone located in the Overworld at x=1, y=2, z=3, the component form is a target of {pos:[1,2,3],dimension:"minecraft:overworld"}.

Under the Hood (skippable). A lodestone compass also renames itself (its base item name is overridden to “Lodestone Compass”), and while it’s in your inventory the game keeps checking whether its lodestone still exists. That polling is why a tracked:true compass goes back to spinning the moment its lodestone is mined.

Maps: map_id, map_color, map_decorations

A filled map is really just a pointer to map data the world stores separately. Three components customise it.

map_id is that pointer, an integer “number of this filled map, representing the shared state holding map contents and markers.” Two maps with the same map_id show the same picture, because they point at the same stored data.

map_color is an integer color for the little map item’s texture tint:

/give @s filled_map[map_color=16711680]

That gives “a filled map with red markings on item texture.” The number 16711680 is how pure red is written as a single packed integer, and it’s worth understanding because the same trick encodes firework colors, leather-armor dye, and more. A packed color squeezes three values (red, green, blue, each 0–255) into one number: red counts in the millions, green in the thousands, blue in the ones. Pure red (255,0,0) comes out as 16711680. You don’t have to do this math by hand; some other components also accept colors written as a hex code like 0x7FFF33 or as a list of three decimals like [0.5, 1.0, 0.2], but map_color itself takes the single integer.

map_decorations puts markers on the map: the little icons for players, banners, monuments, and so on. It’s a set of named icons, each with:

  • type: which icon. There are many, including player, frame, red_marker, blue_marker, target_x, target_point, mansion, monument, the banner_<color> icons, the village_<biome> icons, jungle_temple, and swamp_hut.
  • x and z: the world coordinates of the marker (as decimals).
  • rotation: which way the icon points, 0.0 to 360.0 degrees clockwise from north.

The key you give each decoration is just “an arbitrary unique string identifying the decoration”: any name you like, used so you can tell two markers apart.

Banners and shields: banner_patterns and base_color

A banner’s design is a stack of coloured patterns. The banner_patterns component is a list of those patterns, applied bottom to top. Each entry has a color (the dye color of that layer) and a pattern (which design). For example:

/give @s black_banner[banner_patterns=[{pattern:"triangle_top",color:"red"},{pattern:"cross",color:"white"}]]

That’s a black banner with a red triangle and a white cross laid over it.

base_color sets the background color. It’s mainly used on shields, which can wear a banner design:

/give @s shield[base_color="lime"]

There’s a friendly side effect: a shield with a base_color gets renamed, so base_color=green makes the item show as “Green Shield.” And if you put banner_patterns on a shield without a base_color, the game fills in white as the background automatically.

Armor trim: trim

An armor trim is the decorative edging you apply at a smithing table. The trim component stores which design and which material:

/give @p leather_leggings[trim={"pattern":"host","material":"emerald"}]

That gives “leather pants with the ‘host’ pattern made of emerald.” Two fields: pattern (the ID of the trim pattern) and material (the ID of the trim material, which decides the trim’s colour). Both pattern and material are resource locations naming entries in registries you’ll meet in Chapter 38, where trim patterns and materials are defined; here you’re just naming ones that already exist.

Placing and spawning: block_entity_data and entity_data

Two components let an item carry data that “wakes up” when the item turns into something else.

block_entity_data is NBT applied when the item is placed as a block, but only for blocks that have a block entity (a block with extra stored data, like a spawner, chest, or sign). This example loads a spawner with a spider:

/give @s spawner[block_entity_data={id:"mob_spawner",SpawnData:{entity:{id:"spider"}}}]

Place that block and it’s a working spider spawner. The data must include an id tag naming the block entity type, and it excludes the position tags (x/y/z) and a couple of others: the game fills those in when you place it.

entity_data is the matching idea for things that spawn an entity: spawn eggs, buckets, armor stands, item frames. It’s “NBT applied to an entity when created from an item”:

/give @s armor_stand[entity_data={id:"armor_stand",Small:1b}]

That armor stand spawns small. Like block_entity_data, it must include an id, and a couple of tags are excluded (UUID and Passengers). A fun example shows the trick at its sneakiest, a wolf spawn egg that actually spawns a cat:

/give @p minecraft:wolf_spawn_egg[entity_data={id:"minecraft:cat"}]

What Can Go Wrong? Both block_entity_data and entity_data can add a red message to the item’s tooltip (for operator players only) warning the player that placing it may result in command execution. That’s a deliberate safety feature: Minecraft flags items that could run commands when placed or used. If you see that red warning, the game is just telling you this item carries data that can run commands.

Your data pack’s scratchpad: custom_data

Of every component in this chapter, this is the one you’ll reach for most in the rest of the book. custom_data is, in Minecraft’s own words:

“key-value pairs of any custom data not used by the game, either as an object or a SNBT string.”

That phrase (not used by the game) is the whole point. Every other component means something to Minecraft: enchantments enchants, food feeds, trim decorates. custom_data means nothing to the game at all. It’s a blank notebook the game faithfully carries around on the item but never reads. It’s yours. You decide what goes in it, and only your data pack reads it back out.

/give @s iron_sword[custom_data={foo:1}]

That’s an iron sword secretly tagged with {foo:1}. To you that means whatever you want it to mean: “this is quest item #1,” “this sword has been blessed,” “this is the third key.” You mark items with custom_data, then later check for it. This is exactly how you build custom items that your pack recognises: tag the item on the way out, test for the tag when it’s used.

You first met this idea back in Chapter 17 and Chapter 18, where a predicate could test an item’s components. custom_data is the component you’ll most often test for, and the chapters ahead lean on it constantly. It’s the bridge between “an ordinary-looking item” and “an item my data pack treats specially.”

Modern Minecraft. Long ago, packs faked custom items by abusing the item’s name or a stray NBT tag, and detection was fragile. custom_data is the clean, supported home for “my pack’s private label on this item.” When an old tutorial tells you to match on a custom name to detect a special item, the modern answer is almost always: put a custom_data tag on it instead.

Damage immunity: damage_resistant

damage_resistant makes an item “invulnerable to the specified damage types when in entity form or equipped” (entity form meaning when it’s lying on the ground as a dropped item). This example makes a fireproof cake:

/give @s cake[damage_resistant={types:"#minecraft:is_fire"}]

It has one field, types, and the value is “a damage type tag prefixed with #.” That # should look familiar: it’s the registry-tag reference syntax from Chapter 14. #minecraft:is_fire is a group of fire-related damage types, not a single one. The damage-type registry and its tags are a Chapter 36 topic; here you only need to know the value is a #-prefixed tag naming which kinds of damage the item shrugs off.

A handful of newer specialty components

These are smaller but genuinely useful, and you’ll meet them in tutorials, so it’s worth knowing what they do.

use_remainder: “replaces the item with a remainder item if its stack count has decreased after use.” In plain terms: what’s left behind after you use it. A water bottle becomes an empty bottle; here’s a splash potion leaving gunpowder:

/give @p splash_potion[use_remainder={id:"minecraft:gunpowder"}]

The remainder is a full item stack, so it can carry its own components and count. Here’s cooked chicken that turns into two named bones after eating:

/give @p cooked_chicken[use_remainder={id:"minecraft:bone",components:{custom_name:{text:"Chicken Bone"}},count:2}]

break_sound: the sound that plays “when the item runs out of durability and breaks.” The value is the ID of a sound event:

/give @s diamond_sword[break_sound="item.wolf_armor.break"]

That diamond sword plays the wolf-armor break sound when it finally snaps.

provides_banner_patterns: marks an item so that, placed in a loom, it offers a banner pattern. The value is a banner-pattern tag, prefixed with #:

/give @p diamond[provides_banner_patterns='#minecraft:pattern_item/globe']

That diamond can hand the globe pattern to a banner at a loom.

provides_trim_material: similarly marks an item so it “provides the specified trim material when used in a trimming recipe.” The component name is exact; build the /give line from it, with the value set to the ID of a trim material. Note that the item must also be in the #trim_material tag to work in the built-in recipes.

jukebox_playable: makes an item playable in a jukebox; the value names a jukebox song definition to play, and the song’s artist and title get added to the item’s tooltip. For instance, you can make a diamond that plays Pigstep when inserted into a jukebox. The component name is exact; build the /give line from it, with the value set to the resource location of a jukebox song.

sulfur_cube_content: “the item stored inside the sulfur cube.” It holds a single item stack and adds a gray “Contains: <item>” line to the tooltip. The component name is exact; build the /give line from it, with the value set to a single item stack (the same item-stack shape used by bundle_contents and use_remainder). Confirm the host item’s id in-game if you’re unsure of it.

The mob-customising components: .../variant, .../collar, .../size, .../color

A whole family of components lets a spawn egg or bucket decide which kind of mob it makes. Their names always have a slash: the mob, then the property. They’re called entity variant components, a group of components present in items like spawn eggs, mob buckets, paintings, and item frames, which modify some of the properties of the entity stored within those items. A few examples:

/give @s wolf_spawn_egg[wolf/variant="rusty"]
/give @s wolf_spawn_egg[wolf/collar="blue"]
/give @s salmon_spawn_egg[salmon/size="large"]
/give @s sheep_spawn_egg[sheep/color="blue"]
/give @s axolotl_spawn_egg[axolotl/variant="blue"]

Reading the names: wolf/variant picks the wolf’s breed ("rusty"), wolf/collar sets its collar color, salmon/size is one of small/medium/large, sheep/color and wolf/collar take a dye color, and axolotl/variant names an axolotl type (the example uses "blue"; the full list of variants comes from registries in Chapter 42). There are many more (cat/variant, horse/variant, villager/variant, tropical_fish/pattern, and so on), each following the same mob/property=value shape.

The values these accept (the full list of wolf variants, painting variants, and the like) come from registries you’ll meet in Chapter 42, where mob variants are defined and where you can even add your own. For now: recognise the mob/property naming, and know that the value is the name of an existing variant.

Practice: a custom splash potion

Time to put potion_contents to work. You’re going to build a splash potion (the kind you throw) that bursts into a cloud of your chosen effects. Everything you need is the custom_effects shape you saw earlier: a list of effects, each with an id, an amplifier (level, starting at 0), and a duration in ticks.

Open your test world’s chat bar and run this, noting splash_potion as the item, so it’s throwable:

/give @s splash_potion[potion_contents={custom_effects:[{id:"minecraft:regeneration",amplifier:1,duration:600},{id:"minecraft:speed",amplifier:0,duration:1200}]}]

Read it left to right. It’s a splash potion whose potion_contents carries two custom effects: Regeneration II (amplifier:1) for 600 ticks (30 seconds), and Speed I (amplifier:0) for 1200 ticks (60 seconds). Throw it and anything caught in the splash gets both. Hover the bottle first and you’ll see both effects listed in the tooltip: when present on an item, the mob effects are listed in the item’s tooltip.

Figure (to be captured). the custom splash potion’s tooltip showing Regeneration II and Speed I, and the coloured cloud after it’s thrown

Now the color. You can set a custom color on the potion too. Inside potion_contents, custom_color is an integer, “the overriding color of this potion texture, and/or the particles of the area effect cloud created.” It’s a decimal color number, the same hex-as-decimal trick you met for leather armor and biome water: pick a hex color, convert it to decimal, and drop it in. A vivid magenta (#FF00FF16711935) makes the swirl unmistakable:

/give @s splash_potion[potion_contents={custom_effects:[{id:"minecraft:regeneration",amplifier:1,duration:600},{id:"minecraft:speed",amplifier:0,duration:1200}],custom_color:16711935}]

Without custom_color, a potion’s swirl color is decided by its effects, so even the plain two-effect brew above comes out tinted on its own; custom_color just overrides that with a color you choose.

Try It! Swap in your own effects. Want a “panic potion”? Try custom_effects:[{id:"minecraft:blindness",amplifier:0,duration:200},{id:"minecraft:slowness",amplifier:2,duration:200}]. Want a long, gentle heal? One Regeneration I for a big duration. Remember amplifier:0 is level I, and 20 ticks make a second.

Try It! (give it a name). Combine this chapter with Chapter 22: add a custom_name so your brew reads as something special, e.g. splash_potion[potion_contents={custom_effects:[{id:"minecraft:regeneration",amplifier:1,duration:600}]},custom_name={text:"Potion of Second Wind",color:"light_purple",italic:false}].

What Can Go Wrong

Mixing up enchantments and stored_enchantments. If your enchanted book “doesn’t do anything” when you hold it, that’s correct: stored_enchantments is meant to be inactive until you combine the book with an item in an anvil. Use enchantments (not stored_) when you want the effect to fire from the item itself.

Forgetting the id in block_entity_data / entity_data. Both components require an id tag naming the block-entity type or entity type. Leave it out and the data has nothing to attach to. And don’t be alarmed by the red operator-only warning on the tooltip: that’s the game safely flagging that the item carries placeable/spawnable data.

Expecting custom_data to do something. It won’t, by design. custom_data is data the game never reads. If you tag a sword with custom_data={hero:1b} and expect the sword to behave differently on its own, nothing will happen until your pack tests for that tag (with a predicate, as in Chapter 18) and acts on it. The component is the label; your pack supplies the behaviour.

What You Know Now

You’ve toured the specialty data components: enchantments and stored_enchantments (live vs. dormant), potion_contents (with custom_effects) and potion_duration_scale, the item-holding container and bundle_contents, firework_explosion/fireworks, lodestone_tracker, the map trio map_id/map_color/map_decorations, banner banner_patterns + base_color, armor trim, the place-and-spawn pair block_entity_data / entity_data, the all-important custom_data scratchpad, damage_resistant, and the newer use_remainder, break_sound, provides_banner_patterns, provides_trim_material, jukebox_playable, sulfur_cube_content, plus the mob/variant-style entity variant components. You can read any component’s value shape and write it into a /give command, and you’ve thrown a splash potion you brewed yourself.

That closes Part VI. You now think of an item as a small bundle of named components you can read, write, and combine, the basis for everything from custom items to quest systems. The complete component list lives in Appendix C.

You can now build: custom enchanted gear, custom potions and tipped arrows, pre-filled shulker boxes and bundles, decorated banners, shields, and trimmed armor, lodestone compasses and marked maps, pre-loaded spawners and spawn eggs, and, most importantly, items your own data pack secretly recognises through custom_data.

Chapter 25 — Function Macros and Return Values

What You’ll Build

Every function you’ve written so far does exactly the same thing every time you run it. Your house-building commands from Chapter 2 build the same house in the same spot. Your zombie-summoning /execute from Chapter 4 summons the same zombies. That’s fine for a fixed job, but what if you wanted one function that could summon any mob, at any size, just by telling it which mob and which size when you call it? Writing a separate function for every mob would be madness.

This chapter teaches the two features that turn a function from a fixed script into a real tool. The first is macros: a way to pass values into a function, so a single file can behave a hundred different ways. The second is return values: a way for a function to hand an answer back to whoever called it, so a function can act like a question (“is this player inside the arena?”) and the rest of your pack can react to the answer.

By the end you’ll have two new functions in the mypack pack you started in Chapter 9: mypack:summon_scaled, a macro that summons any mob at any scale, and mypack:in_arena, a function that reports whether you’re standing inside a region. We’ll test both in the test world you set up in Chapter 1.

One function, many behaviors

Think back to how a function runs. Put plainly:

Functions are data pack files, allowing players to run lists of commands.”

Each line is one command, no leading slash, exactly the convention you’ve used since Chapter 9. Up to now those lines have been fixed: whatever you typed in the file is exactly what runs. A macro changes that. A macro lets a line contain a blank that gets filled in with a value you supply at the moment you call the function. Same file, different value, different result.

These special lines are called macro lines:

“Functions can include macro lines, lines preceded by $. Macro lines act similar to normal commands but can reference the compound NBT tag provided when invoking the function with the function command.”

So a macro line is just a normal command line with one difference: it starts with a dollar sign $, and somewhere inside it there’s a placeholder waiting to be filled.

The $ prefix and $(key) substitution

Here’s how the blank works. Inside a macro line, you write $(key) wherever you want a value to appear:

“Values from this compound tag can be referenced with their associated key by using $(<key>) anywhere in the macro line.”

The word key is just the name of the value, like a label on a box. If you pass in a value labelled speed, you write $(speed) in the line, and the game swaps in whatever speed was set to, right before the line runs. The timing is precise:

“Macro lines are evaluated each time before the function executes, substituting the variable specifications with the associated values and parsing the resulting command.”

That last part matters: the substitution happens first, and then Minecraft reads the finished line as a command. So $(speed) doesn’t stay in the command. By the time the command actually runs, the $(speed) has already been replaced by, say, 5.

There’s an exact set of characters a key is allowed to use:

Valid characters for a key are: a-z, A-Z, 0-9, _

In other words, keys follow the same letters-numbers-underscore style you already use for names. Pick clear ones like mob, scale, or target_x.

Here is a worked example. Suppose the function foo:bar contains these three lines:

say This is a normal non-macro command where $(key_1) does not work
$say This is a macro line, using $(key_1)!
$teleport @s ~ ~$(key_2) ~

Look carefully at the difference. The first line has no $ at the start, so it is an ordinary command, and $(key_1) “does not work” there; it would be sent to chat literally. The second and third lines start with $, marking them as macro lines, so their $(key_1) and $(key_2) get filled in.

What Can Go Wrong? Forgetting the $ at the start of the line is the number-one macro mistake. Without it, the line is a plain command and $(key) is left untouched: your say prints the literal text $(key_1) and your teleport fails to parse. The $(...) placeholder only has meaning on a line that begins with $.

Supplying values inline: function mypack:foo {key:value}

A placeholder is no use unless you actually provide the value. The simplest way is to type the values right after the function name, as a compound (the same {key:value} shape you learned for command storage in Chapter 12). This form of the /function command works like so:

function <name> <arguments> — “Runs a function or functions in a tag, with arguments for macros.” The arguments “Specifies arguments for macro functions in a compound NBT tag.”

Here’s an example you can read directly:

“To run a function macro with arguments a=42, b="example": /function custom:example/test {a: 42, b: \"example\"}

So to run the foo:bar from above, supplying both keys, you’d type in chat:

/function foo:bar {key_1:"Example String", key_2:10}

Here’s exactly what that produces. The macro say line prints This is a macro line, using Example String!, and notice the quotes around "Example String" are gone in the output. That’s a rule worth knowing:

“For strings, the value of the string is inserted directly. (That is, without the quotes.)”

And the $teleport @s ~ ~$(key_2) ~ line, with key_2:10, “would teleport you 10 blocks up.” The number 10 is dropped straight into the command. Here’s how each kind of value is inserted:

“For all numeric types, the value is converted to plain text. The type suffix is not included. (For example, 10b is converted to 10)… true and false are equivalent to 1b and 0b, so they are converted to 1 and 0 respectively. For lists, compound tags, and the array types, the canonical SNBT representation is used.”

So a string loses its quotes, a number like 10b loses its b suffix and becomes plain 10, and a whole compound or list is inserted in SNBT form (the NBT text you met in Chapter 12).

Supplying values from the world: the with clause

Typing the compound by hand is great for testing, but often the values you want are already in the world, stored on an entity, in a block, or in command storage. Instead of copying them out by hand, you can point the function straight at that source with the with clause:

“Macro functions can also harness stored NBT data using the with instruction that may follow the function name. The argument succeeding with must specify a NBT source (a block, entity, or command storage) followed by the NBT path of a compound tag.”

The full shape, from the /function command page, is:

function <name> with (block <sourcePos>|entity <source>|storage <source>) [<path>]

Read that as: after with, name where the data lives (a block at some position, an entity, or a storage) and then, optionally, a <path> pointing at the exact compound tag to use. These are the same three NBT sources and the same path idea you used with /data in Chapter 12.

This example reads a value off the player and uses it:

execute as @p run function foo:bar2 with entity @s SelectedItem

where foo:bar2 is the single macro line:

$say The player running this function is holding $(count) items with ID $(id)!

Here with entity @s SelectedItem says “take the argument compound from the SelectedItem data on the entity running this, the item in the player’s hand.” Because that item’s NBT contains count and id keys, the macro’s $(count) and $(id) get filled in with the held item’s stack size and ID. One macro line, and it reports whatever the player happens to be holding.

There’s a storage form too: “To run a function macro with arguments from storage custom:storage: /function custom:example/test with custom:storage.” That points the function at a whole storage compound you’ve filled in earlier, which is exactly how you’ll feed mypack:summon_scaled from your mypack:config storage later in this chapter.

Under the Hood (skippable). Why are macro lines parsed each time before the function executes, while ordinary lines are parsed once when the pack loads? Because a macro line isn’t a finished command until its values are filled in, and those values can be different on every call. So Minecraft waits, builds the real command at the last second, and only then reads it. The cost is small but real: a macro line does a little extra work every single time it runs.

The rules of macros (read these once, save yourself an hour)

There are three rules that catch everyone eventually. Learn them now.

1. Every $(key) you use must be provided.

“The compound tag provided must contain one entry for each variable used in the macro function, but may contain entries not referenced by the macro function. If any variables are not provided, or any commands evaluated from macro lines are unparseable, the entire function is not invoked and no commands in it run.”

Two things to take from that. First, you may pass extra keys the function ignores (harmless). Second, if you forget a key the function needs (or your filled-in line turns out to be a broken command), the function doesn’t run at all, not even the non-macro lines before the broken one. It’s all-or-nothing.

2. A function with any macro line cannot be called bare in a tag. The /function command fails if “There’s any macro line in the function(s)” and no arguments were given. So a macro function can’t just sit in your tick.json and run on its own: it needs its values supplied at call time. (We’ll come back to scheduling and ticking macros in Chapter 26.)

3. Substitution is text, not magic. $(key) is replaced by the value’s text and then parsed. If a value contains something that breaks the command’s grammar, you get an unparseable command and rule 1 kicks in. Keep macro values simple and the substituted line valid.

Functions as questions: return values

Now the second half of the chapter. So far a function just does things. But sometimes you want a function to answer something (“is the player in the arena?”, “did the setup succeed?”) and let the caller decide what to do next. That’s what return values are for.

Here’s the idea:

“After execution, the function can return a return value and a successfulness. The return value is an integer, and the successfulness is failure or success.”

So a function can hand back two things: a return value (a whole number) and a successfulness (success or failure). It does this with the /return command. And if a function never runs a return? There’s a name for that:

“If no return command is executed in the function, the function is a void function that does not return any return value or successfulness.”

A void function is the kind you’ve written all along: it just runs its commands and ends, handing nothing back. That’s still perfectly normal; most functions are void. Returning is something you add only when you want an answer.

/return <value> and return fail

The return command lives inside a function:

“A command that can be embedded inside a function to control its execution. It ends function execution and sets the successfulness and the return value of the function.”

There are three forms. The first two are simple. The return value is an integer and the successfulness is either success or failure (you met both in the void-function note above), so:

  • return <value> hands back an integer. Writing return 1 stops the function on the spot and makes 1 its return value; return 5 does the same with 5.
  • return fail stops the function too, but marks the result a failure.

The key thing both share: the function stops right there. Whichever return runs first wins, and any lines below it don’t run at all.

That “stops right there” is useful on its own, even when you don’t care about the value. Like other commands, a return placed after an execute if/unless can be made conditional, so under different conditions a function can end at different lines, “thus achieving more complex behaviors”: for example, a function that “simulates an if-else statement.” In plain terms: you can guard the rest of a function behind a check and bail out early when the check fails, the same shape as an early exit in real programming.

/return run <command>

The third form is the most flexible. Instead of giving a fixed number, you can have return run a command and hand back that command’s outcome, so return run execute if entity @s[...] returns success-or-failure depending on whether that entity test passed. This is how a function becomes a real question: ask the question with a command, and return run forwards the verdict to whoever called the function. There’s one more wrinkle when the run command branches: a return “can also end a forking execute command that has multiple branches at the first branch.” So if the command is a forking execute, only the first branch runs before the function stops.

Under the Hood (skippable). Every command in Minecraft quietly produces two outputs: a success (did it work?) and a result (usually a count of things it affected). return run is what lets a function adopt a command’s success and result as its own answer; that’s why execute store (below) can capture them. The dependable rules are the ones to lean on: return ends the function, returns an integer, ends a forking execute at the first branch, and the function’s answer can be stored with execute store. If you ever need the exact per-command success/result wording, the wiki’s Return command page lays it out form by form.

Using what a function returns

A returned value is only useful if the caller can read it. There are two ways.

Checking it with execute if function.

“If the function is called by a execute if function command, its return value is checked whether it is not 0.”

So execute if function mypack:in_arena run ... runs the ... only when mypack:in_arena returned a non-zero value. Zero counts as “no”; anything else counts as “yes.” This lets one function ask another a yes/no question and branch on the answer, the function version of the if score and if block checks you learned in Chapters 4 and 11.

Saving it with execute store.

“If the function is called by a function command, the return value and successfulness are returned to the function command as its output values, and then can be stored using execute store.”

So execute store result score @s arena_count run function mypack:count_players captures the function’s return value into a scoreboard score (or into storage). store result saves the return value; store success saves the successfulness (1 or 0). This is how you turn a function’s answer into a number your pack can keep and reuse.

Modern Minecraft. Older tutorials, written before functions could return anything, fake this by having a function scoreboard players set some flag and then checking that flag afterward. You can still do that, but return plus execute if function / execute store is the clean, modern way. One function asks; the caller reads the answer directly. No leftover flag to remember to reset.

Walkthrough: summon any mob at any scale

Let’s build the macro promised at the start. We want one function that summons whatever mob we name, at whatever scale we ask for. Both pieces (the mob’s ID and the scale number) will be macro values.

Create this file:

mypack/data/mypack/function/summon_scaled.mcfunction

$summon $(mob) ~ ~ ~ {attributes:[{id:"minecraft:scale",base:$(scale)}]}

That’s a single macro line (note the leading $). It summons a mob of type $(mob) at your position, and sets its scale attribute’s base value to $(scale). Both placeholders get filled in when you call it. Now run it from chat, inline:

/function mypack:summon_scaled {mob:"minecraft:zombie", scale:2.0}

Substitution turns that macro line into summon minecraft:zombie ~ ~ ~ {attributes:[{id:"minecraft:scale",base:2.0}]}, and a double-size zombie appears. Change the call to {mob:"minecraft:chicken", scale:0.5} and you get a tiny chicken from the very same file. One function, every mob, every size.

Figure (to be captured). a giant zombie and a tiny chicken side by side, both spawned from mypack:summon_scaled

Now feed it from storage instead, to see the with clause work. First stash an argument compound in your mypack:config storage (the storage you created in Chapter 12). Add a small helper:

mypack/data/mypack/function/set_spawn_args.mcfunction

data modify storage mypack:config SpawnArgs set value {mob:"minecraft:cow", scale:3.0}

Run /function mypack:set_spawn_args once, then call the macro pointed at that compound:

/function mypack:summon_scaled with storage mypack:config SpawnArgs

The with storage mypack:config SpawnArgs says “take the {mob,scale} compound from the SpawnArgs path of mypack:config storage.” A giant cow appears, and you never retyped the values. That’s the pattern you’ll reuse whenever the data already lives somewhere in your pack.

Try It! Add a third value, name, and a second macro line: $data modify entity @e[...] CustomName set value ..., or simpler, append ,CustomName:'"$(name)"' thinking carefully about quotes. Pass {mob:"minecraft:zombie", scale:2.0, name:"Brute"}. Remember rule 1: every key you reference must be in the compound, or nothing runs.

Walkthrough: a function that answers a question

Now a returning function. We want mypack:in_arena to answer “is the player standing inside the arena?”, returning 1 for yes and ending void (no value) for no. We’ll define the arena as a fixed box and use return run to forward an entity test.

mypack/data/mypack/function/in_arena.mcfunction

return run execute if entity @s[x=0,y=64,z=0,dx=20,dy=10,dz=20]

One line. execute if entity @s[...] tests whether the running player (@s) falls inside the box that starts at 0 64 0 and stretches 20 blocks along X and Z and 10 up (the dx/dy/dz volume selector from Chapter 3). return run forwards that test’s success straight out as the function’s answer: if you’re inside, the function returns a success; if you’re not, the execute test matches nothing, so the function comes back as a failure rather than a non-zero value, which, to execute if function, counts as “no.”

Now use the answer. Anywhere in your pack you can write:

execute if function mypack:in_arena run say You are in the arena!

Because execute if function checks whether the return value “is not 0,” the say runs only when you’re inside the box. You’ve turned a function into a reusable yes/no question.

Figure (to be captured). chat showing “You are in the arena!” appearing only while the player stands inside the marked region

You can also count with it. To record the verdict as a score:

execute store result score @s in_arena run function mypack:in_arena

store result saves the function’s return value into the in_arena score for @s (1 when inside, 0 when not) so later commands can read the score instead of re-asking.

Under the Hood (skippable). Word order matters when you nest these. return run execute ... puts the return on the outside, so the function always ends here and always produces an answer (a success when the test matches, a failure when it doesn’t), which is what you want for a simple yes/no. Writing it the other way around, execute ... run return run ..., makes the return run only when the execute matches, so a non-match leaves the function running on to whatever comes next. For mypack:in_arena we want a guaranteed answer, so the return run execute form above is the right one.

Practice

  1. Greeting macro. Write mypack:greet_player as a single macro line: $say Welcome, $(name)!. Call it with /function mypack:greet_player {name:"Steve"}. Then call it with the name key missing and confirm the function refuses to run at all (rule 1). Add a second, non-macro line above it (a plain say) and confirm that line also doesn’t run when the macro key is missing — proof that a failed macro aborts the whole function.

  2. Read the held item. Recreate the held-item example: a one-line macro $say You are holding $(count) of $(id)! called with /execute as @p run function mypack:held with entity @s SelectedItem. Hold different items and see the report change. (If you’re holding nothing, SelectedItem is absent and the function won’t run; that’s rule 1 again.)

  3. Early exit with return. Write mypack:height_flag with two lines: first execute if entity @s[y=100,dy=320] run return 1 (return 1 if you’re high up), then a final return 0 for everyone else. Call it with execute store result score @s height_flag run function mypack:height_flag and read the score. Confirm that whichever return fires first wins, and the line below it never runs — proof that return stops the function on the spot.

  4. Combine both ideas. Make mypack:summon_in_arena: first execute unless function mypack:in_arena run return fail (bail out if you’re not in the arena), then a macro $summon $(mob) ~ ~ ~ line. Now it only summons when you’re standing inside the box: a returning function gating a macro function.

What Can Go Wrong

  • You forgot the $ at the start of a macro line. Then $(key) is never substituted: a say prints the literal $(key) text, and most other commands fail to parse. Every line that contains $(...) must begin with $.

  • A key you reference wasn’t supplied. The rule is blunt: “If any variables are not provided… the entire function is not invoked and no commands in it run.” Nothing happens, not even the lines before the macro. Double-check that your {...} compound (or with source) contains every key the function uses. Extra keys are fine; missing ones are fatal.

  • You expected a void function to return something. If your function never reaches a return, it hands back nothing, and execute if function treats it as… a failed check (no non-zero value). If you want a yes/no answer, make sure every path through the function ends in a return, or accept that “no return” reads as “no.”

  • You put the quotes in twice (or zero times). When a string value is substituted, its quotes are removed: "Example String" becomes Example String. If the spot where you wrote $(name) needs quotes (like inside a JSON text component), you must add them around the placeholder yourself. If it doesn’t (like a bare mob ID), don’t.

What You Know Now

You can write a macro line (starts with $, contains $(key) placeholders), and supply its values two ways: inline as a {key:value} compound after the function name, or from a live block, entity, or command storage with the with clause. You know substitution happens just before the line runs, that strings lose their quotes and numbers lose their suffixes, and that a single missing key aborts the whole function. You can make a function answer a question with /return <value>, return fail, or /return run <command>, you know a function with no return is a void function, and you can read a function’s answer with execute if function (non-zero = yes) and save it with execute store result. One function can now behave a hundred ways and report back, the foundation for the timing, randomness, and minigame work coming next.

Chapter 26 — Timing and Randomness

What You’ll Build

So far every function you’ve written runs right now: the moment it’s called, it does its work and finishes. But two of the most fun things in Minecraft happen on their own schedule. A countdown says “3… 2… 1… GO!” with a pause between each number. A loot box gives you a different prize every time you open it. Neither of those is “do it all this instant”: one needs to happen later, and the other needs to be unpredictable.

This chapter teaches the two commands that make those possible. /schedule runs a function after a delay you choose, and a scheduled function can even schedule itself again, which gives you a repeating loop without touching the tick tag. /random rolls random numbers, either quietly (just for you) or out loud (for everyone), and execute store lets a command branch on whatever number came up. By the end you’ll have added two systems to your mypack pack: a delayed-start countdown timer that announces each second by re-scheduling itself, and a loot box that rolls a weighted random prize. This chapter builds on functions (Chapter 9), scoreboards (Chapter 11), and the execute store and execute if score skills from Chapter 4.

Running a function later: /schedule

A schedule is a request you hand to the server: “run this function, but not yet, wait a bit first.” The /schedule command delays the execution of a function: the function is executed by the server after a specified amount of time passes.

Here’s the full Java Edition syntax:

schedule function <function> <time> [append|replace]
schedule clear <function>

The first form adds a schedule; the second removes one. The <function> is the namespaced name of a function you’ve written, exactly the kind of name you’ve been using with /function since Chapter 9, like mypack:countdown_tick. The <time> is how long to wait.

Modern Minecraft. Older tutorials sometimes fake delays by counting ticks in a tick-tag function (“add 1 every tick, and when the count hits 100, do the thing”). That still works, but /schedule is the purpose-built tool: you say when once, and the server remembers for you. Reach for the counting trick only when you genuinely need to check something every tick.

Time units and the 1t surprise

The <time> is a number with a unit letter on the end. The t suffix (for example schedule function <function> 1t) means ticks. A tick is one step of the game loop, and as Chapter 7 recorded, Minecraft runs 20 ticks per second, so an in-game day is 24,000 ticks (about 20 minutes). That gives you a handy conversion: one second is 20t, five seconds is 100t, and so on.

Tip. Ticks are all you need for everything in this chapter (20t for one second, 100t for five), and counting in ticks keeps the timing precise. The game also accepts other unit letters on the <time> argument; if you’re curious which ones, type schedule function <function> in-game and watch the command suggestions that pop up. For this book we’ll stick with t.

There’s one genuinely surprising detail about ticks and scheduling worth a warning: the delay time of 1t does NOT always mean one tick of delay. Instead, it schedules the function for the upcoming phase for scheduled functions. Each game tick has phases, and scheduled functions run in their own phase after the #minecraft:tick functions. So specifying 1t in #minecraft:tick functions makes the function run within the same tick, while specifying 1t in scheduled functions makes the function run in the next tick. You almost never need to think about this, but if a 1t schedule ever fires sooner or later than you expected by a single tick, this is why. For any delay of two ticks or more it behaves exactly as you’d guess.

Canceling a schedule, and append vs replace

What happens if you schedule the same function twice before the first one fires? That’s what the last argument controls. There are two modes:

  • replace (default) simply replaces the current function’s schedule time.
  • append allows multiple schedules to exist at different times.

So replace is the default: if mypack:countdown_tick is already scheduled and you schedule it again, the new time replaces the old one: there’s still only one pending run. Here’s a concrete reason this is useful: if a function is scheduled to be executed in 30 seconds, and before it is executed you want to modify the execution time, you can use replace mode to set a new schedule to replace the original. Use append only when you deliberately want the same function queued to fire at several different times at once.

To cancel a pending schedule before it fires, use schedule clear:

schedule clear <function>

One catch to watch for: the function name here should be a namespaced ID (minecraft: cannot be omitted). In other words, always write the full namespace:path: mypack:countdown_tick, never just countdown_tick.

A loop that schedules itself

Here’s the trick that makes /schedule powerful: a scheduled function can schedule itself. When mypack:countdown_tick runs, its last line can be schedule function mypack:countdown_tick 20t, and now it’ll run again in one second, where it will schedule itself again, forever (or until you tell it to stop). That’s a repeating loop that lives entirely inside one function, with no entry in the tick tag.

So when should you use /schedule versus adding a function to the minecraft:tick tag (the way Chapters 4, 11, and 12 did)?

  • Tick tag when you need to check something constantly, every single tick, 20 times a second (like “is anyone standing on gold?”).
  • /schedule when you want something on a slower or one-off timer: once a second, or once after a 5-second delay. Self-rescheduling at 20t is far gentler on the game than a tick function that runs 20× as often and counts to 20 each time.

Random numbers: /random

The other half of this chapter is unpredictability. The /random command generates a random integer, and it comes in two flavors, which differ by who gets told the result:

random (value|roll) <range>

Here’s the difference: if it is value, the result is displayed in chat only to the player executing the command; if roll, the result is broadcast to all players. So:

  • random value is a quiet roll. You usually want this inside a data pack, because you don’t want spammy numbers in everyone’s chat; you’re going to use the number, not show it.
  • random roll is a loud roll, announced to the whole server. Great for a visible “everybody sees the dice” moment, like a party game.

The <range> is written as two numbers joined by two dots (min..max), and the roll picks one integer from that range, ends included. For example, random roll 1..5 rolls a random number between 1 and 5 and writes it in chat. One rule to know: the size of the range (calculated by max - min + 1) should be between 2 and 2147483646, so you always need at least two possible values.

Branching on a roll with execute store

A number you can’t react to isn’t much use. To make decisions, you capture the roll into a scoreboard and then test it, both skills you already have from Chapters 4 and 11. Here’s exactly that pattern:

execute store result score @p random_number run random value 1..10

This generates a random number between 1 and 10 and stores it in a scoreboard called random_number for the nearest player. execute store result score <target> <objective> run <command> takes whatever number the command produces and writes it into a scoreboard, here the silent random value 1..10. Once the number is in a scoreboard, you branch on it with execute if score ... matches <range>, the range test from Chapter 4: execute if score @p random_number matches 1..3 run ... fires only when the roll landed between 1 and 3. That single idea (roll into a score, then test the score against ranges) is the engine behind the weighted loot box you’ll build below.

Named random sequences: reproducible randomness

Plain random value 1..10 is freshly random every time. But sometimes you want randomness that’s reproducible: the same “random” results every run, like a puzzle map where the layout should be surprising but identical for every player. That’s what a named random sequence is for. Here’s the longer form:

random (value|roll) <range> <sequence>

The <sequence> is the resource location of a random sequence, a namespaced name like mypack:loot, just like a function name. If the sequence does not exist, it is created with the random sequence settings of the world. Each named sequence has its own seed (this seed is called the salt value), so two different sequence names give two independent streams of numbers, and a given sequence with a given seed always produces the same stream.

You control a sequence’s state with random reset:

random reset *
random reset <sequence> [<seed>] [<includeWorldSeed>] [<includeSequenceId>]

random reset * removes all the random sequences in the world. Resetting a single sequence removes and re-creates a random sequence, optionally with a <seed> you choose, which is how you pin a sequence to a known starting point so it replays identically. The two extra flags, <includeWorldSeed> and <includeSequenceId>, both default to true and decide whether the world seed and the sequence’s own name get mixed into the seeding; for now the default behavior is fine.

Under the Hood (skippable). Named sequences are the same machinery loot tables use for their randomness: a sequence is created with the settings of the world when called by a loot table. That’s why two players opening the same loot table can be given reproducible results. You don’t need this for the loot box below (we’ll keep it plainly random), but it’s the door into seeded, shareable randomness when you want it.

Walkthrough: a delayed-start countdown timer

Let’s build a countdown that, when started, waits a moment and then announces “3… 2… 1… GO!”, one number per second, using nothing but self-rescheduling. We’ll track the current number in a scoreboard so each tick knows where it is.

First, an objective to count on. Chapter 11 taught scoreboard objectives add; we’ll create one named countdown. We’ll store the count on a fake player (a score holder that isn’t a real entity, here #timer), the same fake-player trick from Chapter 11 for keeping a single shared number. Here’s the starter function:

mypack/data/mypack/function/countdown_start.mcfunction

scoreboard objectives add countdown dummy
scoreboard players set #timer countdown 3
tellraw @a {"text":"Get ready..."}
schedule function mypack:countdown_tick 20t replace

The last line schedules the tick function to run in 20t (one second) from now. That one-second gap is the “delayed start”: nothing is announced instantly; the first number appears after the pause. We pass replace (the default, written out here so it’s obvious) so that starting the countdown twice doesn’t stack up two overlapping countdowns.

Now the tick function. Each time it runs it announces the current number, counts down by one, and then either schedules itself again or stops:

mypack/data/mypack/function/countdown_tick.mcfunction

execute if score #timer countdown matches 1.. run title @a title {"text":"","extra":[{"score":{"name":"#timer","objective":"countdown"}}]}
execute if score #timer countdown matches 0 run title @a title {"text":"GO!","color":"green"}
scoreboard players remove #timer countdown 1
execute if score #timer countdown matches 0.. run schedule function mypack:countdown_tick 20t replace

Reading it line by line:

  1. If the count is 1 or more (matches 1..), show that number big on screen with title. The {"score":...} text component prints the live value of #timer in the countdown objective, the same score-in-text idea from Chapter 11’s tellraw.
  2. If the count is exactly 0 (matches 0), show “GO!” instead of a number.
  3. Count down by one with scoreboard players remove (Chapter 11).
  4. If the count is still 0 or more (matches 0..) after decrementing, re-schedule this same function for one more second. Once the count drops to -1, this if fails, nothing gets re-scheduled, and the loop quietly stops.

To run it, call function mypack:countdown_start (you’ll wire a trigger for it in the projects of Part VIII; for now, calling it by hand is fine). You’ll see “Get ready…”, a one-second pause, then 3, 2, 1, GO!, each a second apart, driven entirely by /schedule.

Figure (to be captured). the title “3” filling the screen mid-countdown, with “Get ready…” in chat above

Notice there is no new entry in mypack/data/minecraft/tags/function/tick.json. That’s the whole point. The loop runs itself.

Walkthrough: a weighted random loot box

Now the unpredictable system. We want a loot box that, when opened, rolls a number and gives a prize, but not all prizes equally likely. We’ll make common prizes cover a wide range of numbers and rare prizes a narrow one. This is the same “weights” idea you met with loot tables in Chapter 16, done here by hand with ranges.

We’ll roll 1..100 so the math reads like percentages. Say we want: 60% dirt (common), 30% iron (uncommon), 10% a diamond (rare). That maps to ranges 1..60, 61..90, and 91..100. First, roll into a scoreboard:

mypack/data/mypack/function/loot_box_open.mcfunction

scoreboard objectives add loot_roll dummy
execute store result score #roll loot_roll run random value 1..100
function mypack:loot_box_give

The middle line is the execute store result score ... run random value ... pattern from earlier: it rolls a silent number from 1 to 100 and stores it on the fake player #roll in the loot_roll objective. Then it calls the giver function to act on that roll. Now the branching:

mypack/data/mypack/function/loot_box_give.mcfunction

execute if score #roll loot_roll matches 1..60 run give @p minecraft:dirt 16
execute if score #roll loot_roll matches 1..60 run tellraw @p {"text":"Common: a stack of dirt.","color":"gray"}
execute if score #roll loot_roll matches 61..90 run give @p minecraft:iron_ingot 4
execute if score #roll loot_roll matches 61..90 run tellraw @p {"text":"Uncommon: 4 iron!","color":"white"}
execute if score #roll loot_roll matches 91..100 run give @p minecraft:diamond 1
execute if score #roll loot_roll matches 91..100 run tellraw @p {"text":"RARE: a diamond!","color":"aqua"}

Each pair of lines covers one prize: an execute if score ... matches <range> that gives the item, and a matching one that announces it. Because the three ranges (1..60, 61..90, 91..100) don’t overlap and together cover every number from 1 to 100, exactly one prize fires on every open. To change the odds, just resize the ranges: make diamond 96..100 and it drops to a 5% chance. To open the box, call function mypack:loot_box_open.

Try It! Swap random value for random roll in loot_box_open (and drop the execute store, just running random roll 1..100) when you want the number shouted to the whole server before the prize appears, a fun “watch the dice” moment for a party. Use value (silent) for the real, behind-the-scenes roll.

Practice

  1. Auto-restarting countdown. Make countdown_start re-arm itself: after “GO!”, have countdown_tick wait 200t (ten seconds) and then call mypack:countdown_start again, so the countdown loops forever on a ten-second cycle. (Hint: add one more execute if score #timer countdown matches -1 run ... line.)

  2. Cancel button. Write mypack:countdown_cancel that runs schedule clear mypack:countdown_tick so you can stop a running countdown early. Remember the name must be fully namespaced.

  3. Four-tier loot box. Add a fourth prize tier to the loot box (say a 1% “jackpot” of an enchanted golden apple at 100..100) and shrink the other ranges so they still add up to exactly 1..100 with no gaps and no overlaps.

  4. Seeded daily prize. Use a named sequence, random value 1..100 mypack:daily, so the roll comes from a reproducible stream. Then experiment with random reset mypack:daily 12345 and watch the same sequence of rolls repeat: reproducible randomness in action.

What Can Go Wrong

What Went Wrong? “My scheduled function never runs.” The most common cause is the function name. schedule function and schedule clear both want the full namespaced IDmypack:countdown_tick, not countdown_tick. A bare name silently fails to match. Double-check the namespace, and confirm the function file actually exists at data/mypack/function/countdown_tick.mcfunction.

What Went Wrong? “It re-schedules forever and won’t stop.” A self-rescheduling loop only stops if some run skips the re-schedule line. In the countdown, the final execute if score #timer countdown matches 0.. is what stops it: once the count goes below 0, the condition is false and nothing new is queued. If you forget that guard (or write matches .. with no bound), the function re-schedules unconditionally and runs forever. If you’re stuck in a loop, schedule clear <function> cancels the pending run.

What Went Wrong? “The loot box gives two prizes, or none.” This happens when your matches ranges overlap or leave a gap. If 1..60 and 60..90 both include 60, a roll of 60 triggers both prizes; if you write 1..59 and 61..90, a roll of 60 gives nothing. Lay the ranges out so each number from your min to your max is covered by exactly one range, back-to-back, like 1..60, 61..90, 91..100.

Chapter 27 — Advanced /execute Patterns

What You’ll Build

Back in Chapter 4 you learned the /execute command, the one that lets a command run with a different who, where, and when. You met as, at, positioned, if entity, if block, and run, and you chained them together. At the time, two of /execute’s powers were marked “coming later”: execute store (saving a command’s answer somewhere) and execute in (running a command in another dimension). Chapter 12 made the same promise about execute store writing into storage, and about if data using storage as a yes/no test. This is the chapter where all of those promises come due.

But this chapter is about more than filling in three missing subcommands. It’s about patterns: the handful of techniques that data pack authors combine over and over to do things that look, at first, impossible with commands: shooting an invisible ray out of a player’s eyes and reacting to the first block it hits, walking a function forward through the world one step at a time, parking an invisible “pin” at a position and coming back to it later, and running a little machine that remembers what state it’s in. Every one of these is built out of the pieces you already have, snapped together in clever ways.

By the end you’ll have extended your mypack pack with two real tools: a raycaster that shoots a line of particles out of where you’re looking and stops at the first solid block, and a nearest-block finder that searches outward from a point until it finds a block you named. Along the way you’ll write your first recursive functions (functions that call themselves) and wire them safely into your pack.

This chapter extends your mypack pack and uses the test world you’ve used since Chapter 1. It leans hard on three earlier chapters, so keep them in mind: /execute (Chapter 4), command storage and the /data command (Chapter 12), and function macros and /return (Chapter 25).

A quick map of where we’re going

Everything in this chapter is /execute plus things you already know. Here’s the shape of it:

  • Nested execute chains: using more conditions in one chain, and splitting logic across several chains, for “only when all of these are true” rules.
  • execute store: the deep dive. Take the answer a command produces and save it into a score, a storage, an entity, or a block.
  • execute in: run a command as if you were standing in a different dimension.
  • if data: test whether some NBT data exists, and branch on it (the Chapter 12 promise).
  • Raycasting: stepping a function forward along your line of sight with local ^ ^ ^ coordinates, one block at a time, using a function that calls itself.
  • Marker entities: invisible, do-nothing entities that make perfect position pins.
  • Loop patterns: recursive function calls as the data pack version of “repeat”.
  • State machines: a pack that remembers “what mode am I in?” in storage and acts accordingly.

Let’s start with the one that unlocks the rest.

execute store: catching a command’s answer

Here is an idea that changes how you think about commands. Almost every command produces a number when it runs: a hidden answer. Most of the time you never see it. execute store is the subcommand that catches that number and saves it somewhere you choose.

result vs. success

Straight from the wiki: in Java Edition, every branch of an /execute outputs two values after it finishes: a success value and a result value:

  • The success value is always 0 or 1. It answers “did the last subcommand succeed?” 1 means yes, 0 means no.
  • The result value is a fuller answer. It’s the actual number the last command produced, for example how many entities a condition matched, or how long a list is. It’s always an integer; if a command would produce a decimal, it’s rounded down.

The wiki is precise about where these come from: they “come from the last subcommand (may be a condition subcommand or a run subcommand).” So the value you catch is the value of whatever sits at the end of your chain. And: “If in a branch the last subcommand fails, both the two values are 0 in the branch.”

A store subcommand says “when this chain finishes, take the result (or the success) and put it here.” The wiki describes the timing exactly: a store subcommand “is first processed along with other subcommands in the subcommand chain, recording the location to store in. After the last subcommand … is executed, output values are stored in the recorded location.”

The five places you can store

The wiki lists exactly five storage modes:

store (result|success) block   – Stores output value under one of a block's NBTs
store (result|success) bossbar – Stores output value as a bossbar data
store (result|success) entity  – Stores output value under one of an entity's NBTs
store (result|success) score   – Stores output value under a target's score on an objective
store (result|success) storage – Stores output value under one of a storage's NBTs

Four of the five (everything except score) write into NBT, so they share the same shape. Here is the storage one, copied exactly from the wiki:

store (result|success) storage <target> <path> <type> <scale> -> execute
  • <target>: the storage’s resource location, like mypack:config. (For block it’s a position; for entity it’s a single entity.)
  • <path>: the NBT path where the value should go (the dotted paths you learned in Chapter 12, like Settings.last_count).
  • <type>: the number type to save it as. The wiki says it “must be one of byte, short, int, long, float, and double.” These are the SNBT number types from Chapter 12.
  • <scale>: a multiplier applied before storing, “may be negative.” If you don’t want to scale, use 1.

One important detail from the wiki for the four NBT modes: “If the output value is a decimal, it is rounded first and then multiplied by <scale>.” And a handy convenience for storage in particular: “If the storage does not yet exist, it gets created.”

The score mode is the simplest, because a score is already a plain integer, with no type or scale:

store (result|success) score <targets> <objective> -> execute

The wiki’s one-line description: it “overrides the score held by <targets> on the given <objective> with the final command’s output value.” Note overrides: it replaces the score, it doesn’t add to it.

Under the Hood (skippable) Two limits worth knowing, both from the wiki. First, store ... entity “cannot modify player NBT,” exactly like the /data command from Chapter 12: you can store into mobs and other entities but not into a player’s own NBT. Second, if a chain forks (Chapter 4’s word for “runs once per matched entity”) after a store, the store happens on each branch, and when several branches write to the same spot, “the output value of a later-executing branch directly overwrites the output value of the earlier-executed branch, rather than being accumulated.” So the value left behind is the one from the last branch. Storing per-branch into one shared spot does not add them up.

A worked example: count something into a score

The wiki gives this exact example, and it’s the perfect first taste. Read it right to left from the if:

execute as @a at @s store result score @s nearbyRedSheep if entity @e[type=sheep,nbt={Color:14},distance=..20]

Take it apart with your Chapter 4 eyes:

  • as @a at @s: for every player, standing where they stand.
  • if entity @e[type=sheep,nbt={Color:14},distance=..20] is the last subcommand, so its result is the value that gets caught. For an if entity at the end of a chain, the wiki says the result value “is the number of matching entities.” So: how many red sheep are within 20 blocks.
  • store result score @s nearbyRedSheep: catch that count and write it into each player’s nearbyRedSheep score.

After this runs, every player’s nearbyRedSheep score holds the number of nearby red sheep: a fact you computed with a condition, not a counter. That’s the trick: if-style subcommands measure as well as gate execution, and store result is how you keep the measurement.

Modern Minecraft Old tutorials did counting like this with long scoreboard loops: tag every sheep, add 1 per sheep, reset, repeat. execute store result score ... if entity ... does the same job in one line. When you see an ancient guide manually counting entities with /scoreboard players add, this is the modern replacement.

Storing into your pack’s storage

Let’s put store to work in mypack. In Chapter 12 you built a mypack:config storage with a Settings compound. We’ll add a function that measures how many armor stands are nearby and records it into storage, so the rest of the pack can read it later.

mypack/data/mypack/function/count_markers.mcfunction

# Count armor stands within 16 blocks and remember the number in storage.
# The `if entity` at the end of the chain produces, as its result, the
# number of matching entities; `store result storage` catches that number.
execute store result storage mypack:config Settings.nearby_stands int 1 if entity @e[type=armor_stand,distance=..16]

Run mypack:count_markers, then read it back with the Chapter 12 command (typed in chat, so it keeps its slash):

/data get storage mypack:config Settings.nearby_stands

You’ll see the count. Notice we asked for it as an int with scale 1: a plain integer, no scaling. Because storage auto-creates, this works even on a fresh world where Settings didn’t have a nearby_stands key yet.

if data: testing whether data exists

Chapter 12 promised that execute if data would let you use storage as a condition. Here it is. The wiki: (if|unless) data “checks whether the targeted block, entity or storage has any data tag for a given path.” It comes in three forms, copied exactly:

(if|unless) data block <pos> <path> -> [execute]      – for data checking a block
(if|unless) data entity <target> <path> -> [execute]  – for data checking an entity
(if|unless) data storage <source> <path> -> [execute] – for data checking a storage

The key word is exists. if data does not compare a value to anything; it only asks “is there any data at this path?” If the path is there, if continues (and unless stops); if it’s absent, if stops (and unless continues).

This is exactly what you need for “have I set this up yet?” checks. Add a guard to your pack so a function only runs once the config exists:

mypack/data/mypack/function/needs_config.mcfunction

# Only announce readiness if the config has actually been built.
# `if data storage` tests that the path EXISTS, nothing more.
execute if data storage mypack:config Settings run say Config is ready.
execute unless data storage mypack:config Settings run say No config yet — run mypack:config_init first.

The wiki also shows if data reaching into an entity’s NBT. This example from the page kills zombies wearing no helmet by testing whether the helmet slot has any data:

execute as @e[type=zombie] unless data entity @s ArmorItems[3].id run kill @s

unless data entity @s ArmorItems[3].id reads as “unless this zombie has something in helmet slot 3,” i.e. only when the helmet slot is empty. That’s if data doing real in-game work, well beyond a setup guard.

execute in: reaching into another dimension

The second Chapter 4 promise. execute in <dimension> lets a command run as though it were happening in a different dimension. The wiki’s description: it “sets the execution dimension and execution position.” Syntax, copied exactly:

in <dimension> -> execute

The <dimension> is the dimension’s ID, like minecraft:the_nether, minecraft:the_end, or minecraft:overworld. (Custom dimensions are a Part XI topic, Chapter 44, but execute in works with them too.)

There’s one subtlety the wiki is careful about, and it matters: coordinate scaling between the Overworld and the Nether. The wiki says in “respects dimension scaling for relative and local coordinates: the execution position (only the X/Z part) is divided by 8 when changing from the Overworld to the Nether, and is multiplied by 8 when vice versa.” This is the same 8:1 ratio you know from Nether travel in normal play: one block in the Nether covers eight in the Overworld.

The wiki’s worked examples show this clearly. To teleport a player to the matching spot in the Nether (same numbers), you pin the position first with positioned as @s:

execute in minecraft:the_nether positioned as @s run tp ~ ~ ~

The wiki: “If a player at position (16,64,16) in Overworld runs the following command, the player is teleported to (16,64,16) in the Nether.” The positioned as @s grabs the literal coordinates before the dimension switch, so no scaling is applied.

Without that positioned as @s, the scaling kicks in:

execute in minecraft:the_nether run tp ~ ~ ~

The wiki: “If a player at position (16,64,16) in Overworld runs the following command, the player is teleported to (2,64,2) in the Nether.” The X and Z were divided by 8.

Let’s add a small dimension probe to mypack. It uses execute in together with the if dimension condition (also on the execute page: it “tests the dimension of the execution”) to report what’s loaded where.

mypack/data/mypack/function/dimension_check.mcfunction

# Report which dimension the runner is in, using `if dimension`.
execute if dimension minecraft:overworld run say You are in the Overworld.
execute if dimension minecraft:the_nether run say You are in the Nether.
execute if dimension minecraft:the_end run say You are in the End.

Try It! Combine in with the store you just learned. execute in minecraft:the_end store success storage mypack:config Settings.end_loaded byte 1 if loaded ~ ~ ~ tries to test whether the chunk at your matching End position is loaded, and records a 1 or 0 into storage. (if loaded is another condition on the execute page; it “checks if chunks at a given position is fully loaded.”) This is how map-makers detect whether a far-off dimension is ready before acting in it.

Nested execute chains for multi-condition logic

You already chain subcommands. The “advanced” part is just doing it with intent. Two facts from the execute page make multi-condition logic work:

  1. You can use condition subcommands more than once in a chain. The page says subcommands other than run “can be arranged arbitrarily and used multiple times.” So if ... if ... if ... run is perfectly legal, and every if must pass for the chain to reach run. That’s “AND” for free.
  2. A branch that fails a condition simply stops. The page: “When not at the end of the subcommands chain, only if the condition tests pass does the branch continue; otherwise it terminates.” So stacking ifs narrows things down, step by step.

Here’s a multi-condition rule for mypack: give a player Glowing only when they’re a real player, standing on a gold block, and it’s the Overworld. (You used Glowing back in Chapter 13; the gold block test echoes Chapter 4.)

mypack/data/mypack/function/triple_check.mcfunction

# Every condition must pass before the effect is granted.
# `~ ~-1 ~` is the block just below each player's feet (Chapter 2 relative coords).
execute as @a at @s if dimension minecraft:overworld if block ~ ~-1 ~ minecraft:gold_block run effect give @s minecraft:glowing 5 0

What about “OR”? The execute page’s forking note tells you the clean way: when you need any of several conditions, you don’t cram them into one chain. You write several chains, often one per line in a function, and let each fire independently. That’s the data pack version of an “or”:

mypack/data/mypack/function/danger_floor.mcfunction

# OR logic: two separate chains. A player standing on EITHER block gets the message.
execute as @a at @s if block ~ ~-1 ~ minecraft:magma_block run say Hot floor!
execute as @a at @s if block ~ ~-1 ~ minecraft:lava run say Hot floor!

Under the Hood (skippable) There’s a third option for “or” that you met in Chapter 25: execute if function. The execute page says if function “checks if function(s) are non-void and the return value is non-zero.” So you can push complicated “is any of this true?” logic into a function that uses /return to answer 1 or 0, then test it with one clean if function. Reach for that when a single line of stacked conditions gets too long to read.

Marker entities: invisible position pins

Several of the patterns coming up need a way to remember a spot in the world: a pin you can drop now and teleport back to, or run commands at, later. The tidy tool for that is the marker entity.

The wiki describes the marker plainly: “Markers are entities intended for use in data packs and map-making. They can only be created with the summon command.” Their whole point is to have almost no behavior, copied from the wiki:

“Markers are intended to have minimal behavior. Markers do not move, do not take damage, and cannot be given status effects. Markers do not make sounds … Markers only exist on the server side, so they do not render.”

That list is exactly why they make good pins. A marker sits at a position and does nothing: it won’t drift, won’t get hurt, won’t be visible, and won’t shove blocks or players around (the wiki: markers “do not obstruct the placement of blocks, nor do they push players or other entities away from their own position”). The wiki even notes they “do not count toward the E-value (total amount of entities) listed on the debug screen,” so a few markers won’t clutter your entity count.

Because a marker is just an entity, everything you already know about entities applies: you can give it a tag with /tag (Chapter 13) to find it again, select it with @e[type=marker,tag=...] (Chapter 3), run commands at it (Chapter 4), and read or write its NBT with /data (Chapter 12).

Here’s a pair of functions for mypack: one drops a tagged marker where you’re standing, the other teleports you back to it.

mypack/data/mypack/function/drop_pin.mcfunction

# Summon a marker at the runner's position and tag it so we can find it again.
# Markers can ONLY be made with summon (per the wiki).
execute at @s run summon minecraft:marker ~ ~ ~ {Tags:["mypack_pin"]}
say Pin dropped.

mypack/data/mypack/function/goto_pin.mcfunction

# Teleport the runner to the saved pin. `at` the marker makes its position
# the execution position; tp ~ ~ ~ lands the player exactly there.
execute as @s at @e[type=minecraft:marker,tag=mypack_pin,limit=1] run tp @s ~ ~ ~

What Went Wrong? Dropping pins over and over leaves a pile of markers stacked on old spots, and goto_pin only uses one of them. When you’re done with a pin, clear it: execute run kill @e[type=minecraft:marker,tag=mypack_pin] removes every marker carrying that tag. A good habit is to clear old pins right before dropping a new one, so there’s never more than one.

Under the Hood (skippable) One marker quirk the wiki calls out: “Using F3+I while aiming at a marker does not copy the entity data to the clipboard.” F3+I is the debug shortcut from Chapter 10 that copies an entity’s data. Because markers are server-side only, it won’t work on them. To inspect a marker, use /data get entity @e[type=minecraft:marker,tag=mypack_pin,limit=1] instead.

Recursion: a function that calls itself

Now the big idea behind raycasting and loops. A .mcfunction runs its lines top to bottom and stops. There’s no built-in “repeat 10 times.” So how do data packs loop? A function calls itself. That’s recursion: a function whose job includes running itself again, usually after moving a little or counting down, until some condition says “stop.”

You already have every piece. From Chapter 25’s /function command, a function runs another with function <namespace>:<name>. There’s nothing stopping that “another” from being the same function. And you have /return (Chapter 25) and if/unless conditions to decide when to stop.

The shape of every recursive function in this chapter is the same:

  1. Check a stop condition first. If we should stop, stop (often with /return).
  2. Do one step of work.
  3. Move the context forward (one block, or one counter tick).
  4. Call myself again to do the next step.

The “stop condition first” rule is the most important one. A recursion with no stop is an infinite loop, and Minecraft will cut it off: there’s a hard limit on how many commands a chain of function calls may run in a single tick (the maxCommandChainLength game rule). Hitting that limit means your function silently stops partway, which looks like a bug. Always give recursion a way to end.

What Went Wrong? The single most common recursion mistake is forgetting the stop condition, or putting it after the self-call instead of before. If your function “does nothing” or the game hitches when you run it, you’ve probably written a loop with no exit. Read your function top to bottom and ask: “what line makes this stop calling itself?” If you can’t point to one, that’s the bug.

Raycasting: stepping along your line of sight

A raycast is the technique of shooting an invisible line (a ray) out from a point in a direction, moving forward in small steps and checking each step for something (a block, an entity, a spot to mark). Minecraft has no “raycast” command. You build one out of recursion and the local coordinates from Chapter 2.

The engine is two facts you already know:

  • Local coordinates step forward. The Coordinates page: a caret offset is “an offset within a moving, entity-centric frame … with +Xlocal directed to its left, +Ylocal directed upward, and +Zlocal directed in the direction the sender faces.” And: “tp ^ ^ ^5 teleports the player 5 blocks forward.” So ^ ^ ^1 means one block forward, in the direction you’re facing.
  • execute positioned moves the spot a command runs at without changing anything else (the execute page: it “sets the execution position, without changing execution rotation or dimension”). positioned ^ ^ ^1 therefore nudges the execution point one block forward along the current facing, and keeps the rotation, so the next ^ ^ ^1 keeps going the same way.

Put them together and you get a step: “from here, move one block forward, do something, then call myself to take the next step.” Each call advances the ray one block. We stop when we either hit a solid block or run out of range.

First, the function that fires the ray. It records a step budget into storage (so we don’t loop forever) and aims along the runner’s eyes.

mypack/data/mypack/function/raycast_start.mcfunction

# Begin a raycast from the player's EYES, looking where they look.
# `anchored eyes` recenters local coordinates on the eyes, so ^ ^ ^ starts
# at eye level (per the execute page). We give the ray a budget of 30 steps
# stored in mypack:config, then hand off to the recursive stepper.
data modify storage mypack:config Ray.steps_left set value 30
execute as @s at @s anchored eyes positioned ^ ^ ^ run function mypack:raycast_step

Now the recursive stepper. This is the heart of the chapter: read the comments line by line.

mypack/data/mypack/function/raycast_step.mcfunction

# One step of the ray. Runs AT the current point along the line of sight.

# Pull the remaining budget OUT of storage into a fake-player score so we can
# test and change it. `store result score` catches data get's answer (Ch12:
# data get returns the value at the path).
execute store result score #ray mypack_zero run data get storage mypack:config Ray.steps_left

# STOP CONDITION 1: out of budget. If the score has reached 0, stop.
execute if score #ray mypack_zero matches 0 run return 0

# STOP CONDITION 2: we hit something solid. Mark the spot, then stop.
execute unless block ~ ~ ~ minecraft:air run particle minecraft:flame ~ ~ ~ 0 0 0 0 1 force
execute unless block ~ ~ ~ minecraft:air run return 0

# DO ONE STEP OF WORK: draw a particle at this point along the ray.
particle minecraft:end_rod ~ ~ ~ 0 0 0 0 1 force

# COUNT DOWN: subtract one and write the new budget BACK into storage.
scoreboard players remove #ray mypack_zero 1
execute store result storage mypack:config Ray.steps_left int 1 run scoreboard players get #ray mypack_zero

# MOVE FORWARD ONE BLOCK and CALL MYSELF for the next step.
execute positioned ^ ^ ^1 run function mypack:raycast_step

There’s a lot of Chapter 27 in those lines, so let’s name each move:

  • We load the budget out of storage first with store result score #ray mypack_zero run data get .... #ray is a fake player (the # prefix hides it from the sidebar, Chapter 11), and store result score catches data get’s answer (the value at the path) into it.
  • The stop conditions come right after, exactly as the recursion rule demands. We stop if the budget has hit 0 (if score #ray mypack_zero matches 0), and we stop the moment a step lands on a non-air block (unless block ~ ~ ~ minecraft:air). On a hit we drop a bright flame particle so you can see where the ray landed, then return.
  • The work is one end_rod particle at the current point: that’s what draws the visible line. We use force so it shows even on low particle settings (the particle page: force “always shown even if the ‘Particles’ option … is ‘Minimal’”).
  • The count down writes the new budget back: subtract one from #ray, then store result storage files it into Ray.steps_left. Together with the load-out at the top, that’s store doing exactly what it’s for: moving a command’s numeric answer between a score and storage.
  • The move + self-call is the one line that makes it a ray: positioned ^ ^ ^1 run function mypack:raycast_step. Forward one block, then do it all again from the new spot.

We need the scoreboard objective the stepper uses. Add it to the setup function that runs on load (the mypack:score_setup you built in Chapter 11):

mypack/data/mypack/function/ray_setup.mcfunction

# Objective used as scratch space by the raycaster's countdown.
scoreboard objectives add mypack_zero dummy

Wire it into load by appending to your existing load tag (never rewrite the file: append, keeping the earlier entries):

mypack/data/minecraft/tags/function/load.json

{
  "values": [
    "mypack:load",
    "mypack:score_setup",
    "mypack:ray_setup"
  ]
}

To try it: in your test world, look in a direction with a wall or hill a little way off, and run /function mypack:raycast_start. You’ll see a line of end_rod particles shoot from your eyes and a flame puff appear on the first block the ray meets.

Figure (to be captured). a line of white end_rod particles streaming from the player’s eyes across a field, ending in a flame particle on the face of a distant dirt cliff

What Went Wrong? The ray goes straight through walls. Almost always this is the air test. unless block ~ ~ ~ minecraft:air only stops on a block named exactly minecraft:air; if the very first step starts inside you or a block, the geometry can skip it. Make sure raycast_start uses anchored eyes so the ray begins at eye level, in open space, looking outward.

The ray is too short or hangs. The whole ray runs inside one tick, so a giant budget can bump the maxCommandChainLength limit and cut off. Thirty steps is a safe, generous default; raise it only if you need a longer reach and watch for the ray stopping early.

The raycast searches in one direction. The other classic pattern searches in all directions at once: start at a point and check farther and farther out until you find a block you’re hunting. This is the nearest-block finder, and it uses the same recursion shape with a different “step.”

The idea: keep a search radius in storage. Each round, test a ring of positions at the current radius for the target block; if found, mark it and stop; if not, grow the radius and recurse. A full spherical scan is a lot of positions, so for a beginner-friendly version we’ll search the ring of blocks straight out along the four compass directions at each radius: enough to feel the pattern without writing hundreds of lines.

mypack/data/mypack/function/find_start.mcfunction

# Start a search outward from the runner for diamond_block, up to radius 8.
data modify storage mypack:config Find.radius set value 1
data modify storage mypack:config Find.max set value 8
execute at @s run function mypack:find_step

mypack/data/mypack/function/find_step.mcfunction

# One ring of the expanding search, run AT the search origin.
# Pull radius and max out of storage into scores so we can compare them.
execute store result score #r mypack_zero run data get storage mypack:config Find.radius
execute store result score #max mypack_zero run data get storage mypack:config Find.max

# STOP CONDITION: searched past the maximum radius without a hit.
execute if score #r mypack_zero > #max mypack_zero run say Nothing found within range.
execute if score #r mypack_zero > #max mypack_zero run return 0

# CHECK THE RING: four compass points at the current radius. A hit marks the
# spot with a marker pin and stops. We use a macro so one line covers all four
# offsets — `with storage` feeds the radius in (Chapter 25 macros).
function mypack:find_ring with storage mypack:config Find

# GROW THE RADIUS by 1 and recurse for the next, wider ring.
scoreboard players add #r mypack_zero 1
execute store result storage mypack:config Find.radius int 1 run scoreboard players get #r mypack_zero
execute if data storage mypack:config Find.searching run function mypack:find_step

The ring check is a macro function (Chapter 25): the $(radius) placeholder gets filled in from the storage we passed with with storage mypack:config Find. Each line tests one compass direction at the current radius and, on a hit, drops a marker pin and clears the searching flag so the loop ends.

mypack/data/mypack/function/find_ring.mcfunction

# Macro: test four blocks at distance $(radius) for diamond_block.
# Lines beginning with $ are macro lines; $(radius) is replaced at call time.
$execute if block ~ ~ ~$(radius) minecraft:diamond_block positioned ~ ~ ~$(radius) run function mypack:find_hit
$execute if block ~ ~ ~-$(radius) minecraft:diamond_block positioned ~ ~ ~-$(radius) run function mypack:find_hit
$execute if block ~$(radius) ~ ~ minecraft:diamond_block positioned ~$(radius) ~ ~ run function mypack:find_hit
$execute if block ~-$(radius) ~ ~ minecraft:diamond_block positioned ~-$(radius) ~ ~ run function mypack:find_hit

mypack/data/mypack/function/find_hit.mcfunction

# Runs AT a found diamond block. Pin it and stop the search.
summon minecraft:marker ~ ~ ~ {Tags:["mypack_found"]}
data remove storage mypack:config Find.searching
say Found a diamond block!

Two more pieces tie it together. The search needs a searching flag set when it begins, so find_start should set it. Update that file to:

mypack/data/mypack/function/find_start.mcfunction

# Start a search outward from the runner for diamond_block, up to radius 8.
data modify storage mypack:config Find.radius set value 1
data modify storage mypack:config Find.max set value 8
data modify storage mypack:config Find.searching set value 1b
execute at @s run function mypack:find_step

Now the recursion has a clean stop: each find_step continues only if data storage mypack:config Find.searching, and find_hit removes that flag the instant a block is found. If nothing is found by the time the radius passes max, the > #max check ends it instead.

To try it: in your test world, place a diamond_block a few blocks away from you (on the same Y level for this simple version), stand near it, and run /function mypack:find_start. When the expanding rings reach it, you’ll get “Found a diamond block!” and an invisible marker pinned on it, which you can then teleport to with the same trick as goto_pin.

Figure (to be captured). chat showing “Found a diamond block!” with a diamond block sitting a few blocks from the player in a flat test world

Try It! This version only searches along the compass lines at each radius, on one Y level. Extend the macro find_ring with more $execute if block lines to also check ~ ~$(radius) ~ and ~ ~-$(radius) ~ (up and down), and you’ll have a 3D plus-shaped search. Every new direction is one more macro line; the recursion that grows the radius doesn’t change at all.

State machines: a pack that remembers its mode

The last pattern ties storage and conditions together into something that behaves differently depending on what’s happened before. A state machine is just that: a thing that’s always in exactly one state (a named mode), does work according to that state, and transitions to another state when something happens. You already have everything to build one: the “current state” is a value in storage, and if data/score comparisons choose what to do.

Let’s give mypack a tiny three-state machine: a mini “game” that cycles idlerunningfinished → back to idle. We store the state as a string in mypack:config.

mypack/data/mypack/function/game_init.mcfunction

# Put the machine into its starting state.
data modify storage mypack:config Game.state set value "idle"
say Game ready (state: idle).

A single “advance” function reads the current state and moves to the next one. This is the transition table, written as one chain per state:

mypack/data/mypack/function/game_advance.mcfunction

# Read the state and transition to the next. `if data ... { ... }` matches a
# compound value: it only continues when state equals the given string.
execute if data storage mypack:config Game{state:"idle"} run data modify storage mypack:config Game.state set value "running"
execute if data storage mypack:config Game{state:"idle"} run say Game started! (idle -> running)

execute if data storage mypack:config Game{state:"running"} run data modify storage mypack:config Game.state set value "finished"
execute if data storage mypack:config Game{state:"running"} run say Game over! (running -> finished)

execute if data storage mypack:config Game{state:"finished"} run data modify storage mypack:config Game.state set value "idle"
execute if data storage mypack:config Game{state:"finished"} run say Reset. (finished -> idle)

The clever bit is the path Game{state:"idle"}. That’s an NBT path with a compound filter from Chapter 12; it only matches if Game contains state:"idle". So if data storage mypack:config Game{state:"idle"} is true only when the current state is exactly “idle.” Each pair of lines is one transition: “when in this state, do this, then switch.” Run mypack:game_init once, then run mypack:game_advance repeatedly and watch it walk idle → running → finished → idle.

Under the Hood (skippable) Why a state machine instead of a pile of scoreboard flags? Because the state lives in one place and is always exactly one value, you can never get into a confused “both running and finished” situation. Chapter 12’s mnemonic was “scoreboards count; storage remembers”: a state machine is storage remembering which mode the pack is in. Real minigames (Chapter 33) are built on exactly this idea, usually with the per-tick logic for each state in its own function.

Practice

These extend the two tools you built. Do them inside mypack.

  1. Raycast that paints. Change raycast_step so that on a hit (the unless block ~ ~ ~ minecraft:air branch) it adds to the flame by setblocking a minecraft:glowstone one step back along the ray (positioned ^ ^ ^-1 run setblock ~ ~ ~ minecraft:glowstone). Now you have a “place a light where I’m looking” tool. (Hint: stop before you place, so you don’t overwrite the block you hit.)

  2. Measure the gap. Write mypack:range_check that raycasts forward but, instead of drawing particles, counts how many steps it took to reach a block and stores that number in mypack:config Ray.distance with execute store result storage. You’ll reuse the countdown pattern: the distance is 30 minus steps_left at the moment of the hit.

  3. A four-state machine. Add a paused state to the game machine, between running and finished, so the cycle becomes idle → running → paused → finished → idle. You only add one more pair of lines to game_advance, following the exact compound-filter pattern.

  4. Cross-dimension pin. Combine markers and execute in: write mypack:pin_nether that drops a mypack_pin-tagged marker at your matching position in the Nether using execute in minecraft:the_nether positioned as @s run summon minecraft:marker ~ ~ ~ {Tags:["mypack_pin"]}, then teleport to it with your goto_pin trick (widening its selector to find the marker in any dimension).

What Can Go Wrong

  • store caught the wrong number. Remember the value comes from the last subcommand in the chain. If you write store result ... run say hi, you’ll store say’s output, not the count you meant. The measuring if/condition has to be the final link. Re-read the chain right-to-left and ask “what’s the last thing, and what number does it produce?”

  • A recursion does nothing or stutters. That’s a missing or misplaced stop condition (or a too-big step budget hitting maxCommandChainLength). Every recursive function must check its stop condition before it calls itself, and the call must move something forward (position or counter) so the stop condition eventually fires.

  • execute in teleports to the wrong spot. If a player lands at scaled-down coordinates when you wanted the same numbers, you forgot positioned as @s. The wiki’s rule: plain in ... run tp ~ ~ ~ applies the 8:1 Nether scaling; pin the position with positioned as @s first to keep the literal coordinates.

What You Know Now

You can catch the hidden answer of any command with execute store result/success and file it into a score, storage, entity, or block, including the result-vs-success distinction, the number types, and the scale. You can test for data with if data, branch on whether a path exists, and reach into other dimensions with execute in (Nether scaling and all). You can stack conditions for “AND” logic and split chains for “OR.” You met the marker entity (the invisible, do-nothing position pin) and you wrote your first recursive functions, using them to build a raycaster along your line of sight and an expanding nearest-block finder. And you turned storage into a state machine that remembers what mode your pack is in. These are the core moves behind nearly every advanced data pack; you’ll lean on all of them in the project chapters ahead.

This closes Part VII. You now have functions that take input (macros, Chapter 25), control their own timing and randomness (Chapter 26), and reshape the who/where/when/answer of every command they run (this chapter). That’s the full programmable-function toolkit. Time to build real things with it.

Chapter 28 — Resource Pack Fundamentals

What You’ll Build

Way back in Chapter 7 you learned a slogan: resource packs change appearance; data packs change behavior. Every chapter since then has been about the behavior half: commands, loot tables, components, all of it living inside the data/ folder of your mypack pack. This chapter finally opens the other door. A resource pack is the part of Minecraft that controls how the game looks and sounds: the textures painted on blocks and items, the 3D models, the sound effects, the languages and on-screen text. It changes none of the rules; it only changes the skin over them.

By the end of this chapter you’ll have built a small, working resource pack that sits next to your data pack, with its own marker file and its own folder for art. You’ll write a resource-pack pack.mcmeta (the same kind of marker file you wrote in Chapter 9, used for the other kind of pack), lay out the assets/<namespace>/ folder tree, learn the name of every standard art folder inside it (textures/, models/, items/, lang/, sounds/), and bundle the whole pack with your test world so it loads automatically. You won’t draw any textures yet; that’s the next chapter. Here you’re building the empty rooms, and Chapters 29 and 30 move the furniture in.

This chapter assumes the pack.mcmeta marker file and the folder hierarchy from Chapter 9, the namespace and snake_case naming rules from Chapter 8, and the test world you’ve used since Chapter 1. It does not assume anything from the advanced parts of the book. If you’ve read Part III, you’re ready.

A resource pack is the twin of a data pack

You already know one half of this picture cold. A data pack is a folder with a pack.mcmeta marker file and a data/ folder full of JSON files that configure or add behavior (recipes, loot tables, functions, tags). A resource pack is its mirror image: a folder with a pack.mcmeta marker file and an assets/ folder full of files that change appearance and sound. As the wiki puts it, the resource pack system “provides a way for players to customize textures, models, music, sounds, languages, texts such as the End Poem, splashes, credits, and fonts without any code modification.”

That last phrase, without any code modification, is the whole point. A resource pack only swaps out what things look like and sound like. It can’t add a recipe or summon a mob. That’s why the two systems are separate folders with separate jobs, and why the same custom item often needs both: a data pack to give it its behavior, and a resource pack to give it a custom look. (You’ll see exactly that team-up in Chapter 29.)

Modern Minecraft Older tutorials sometimes call resource packs “texture packs.” That was their name years ago, back when they really did only hold textures. They do far more now (models, sounds, languages, fonts), so the modern name is resource pack. If a guide says “texture pack,” it means this same thing.

Here’s the side-by-side you’re aiming for by the end of the chapter. Two folders, two jobs:

mypack/                         <- your DATA pack (behavior), from Chapter 9
  pack.mcmeta
  data/
    mypack/
      function/
      recipe/
      ...

myassets/                       <- your RESOURCE pack (appearance), built this chapter
  pack.mcmeta
  assets/
    mypack/
      textures/
      models/
      items/
      lang/
      sounds/

Notice the symmetry. The data pack has data/; the resource pack has assets/. Each has its own pack.mcmeta. Each uses your mypack namespace inside. They are two completely separate packs that happen to work as a team, and Minecraft loads them through two completely separate menus, which is a detail we’ll come back to so it doesn’t trip you up.

The marker file: the same pack.mcmeta you already know

Here is the genuinely good news: the file that marks a resource pack is the exact same kind of file you wrote in Chapter 9 to mark your data pack. The wiki is blunt about it: the pack.mcmeta file “is used to define metadata of a resource pack or data pack. The presence of this file identifies a directory or ZIP archive file as a resource pack or data pack.” One file format, two pack types.

It even uses the same version mechanism you learned in Chapter 9. Remember min_format and max_format, the two numbers that say which range of Minecraft versions your pack is built for? Those work identically here. The wiki confirms the change applies to both kinds: “Since 25w31a, the pack format has been changed to work with min_format and max_format fields instead of the previous pack_format field. Both data packs and resource packs have this change.”

So your resource pack’s marker file looks just like your data pack’s marker file. Create the folder myassets (anywhere convenient for now; we’ll move it into the world in a moment), and inside it make this file:

myassets/pack.mcmeta

{
  "pack": {
    "description": "My first resource pack",
    "min_format": 88,
    "max_format": 88
  }
}

That’s a complete, valid resource-pack marker. The three pieces should look familiar from Chapter 9:

  • description — the text shown when you hover over the pack’s name in the menu. Make it whatever you like.
  • min_format / max_format — the version range. We’re using 88 for both, which the wiki’s worked example gives as the recommended setting “for Minecraft 1.21.9 and newer.” A single number like 88 means major version 88, and you can also write a pair like [88, 0] for major-and-minor.

Modern Minecraft Just like with data packs, older resource-pack tutorials use a single pack_format field instead of min_format/max_format. The wiki notes those legacy fields (pack_format, supported_formats) are only needed when a pack also supports old versions (specifically resource pack format below 65) and otherwise “must be absent.” For a brand-new pack aimed at current Minecraft, use min_format/max_format and leave the old field out, exactly as you did in Chapter 9.

This pack.mcmeta is, importantly, the only mandatory file in a resource pack. The wiki’s directory listing marks it plainly: “Metadata of the resource pack. This is the only mandatory file.” A resource pack with nothing but a pack.mcmeta is a perfectly valid (if empty) resource pack: the game will load it without complaint. Everything else you add is optional art.

One optional extra: pack.png

There’s one more file worth knowing about, and it’s optional: pack.png. It’s “the picture to display next to the resource pack in the ‘Select Resource Packs’ screen”: basically the pack’s icon/thumbnail. It goes in the pack’s root folder, right beside pack.mcmeta. If you skip it, your pack just shows a default image. We’ll add one in the Practice section.

The assets/ folder: where the art lives

Inside your resource pack, all the actual appearance files live under one top-level folder called assets/. This is the resource pack’s version of the data pack’s data/ folder: same idea, different name. And just like data/, the first thing inside assets/ is a namespace folder.

You already know namespaces from Chapter 8: minecraft: is the game’s own namespace, and your pack gets its own (mypack). The wiki spells out the rule for resource packs: inside assets/ is a “Directory of the namespace to use… More than one directory for different namespaces may exist under the assets directory. The minecraft namespace is used for vanilla files and can be used to override them.” So assets/minecraft/ is how you’d repaint a vanilla texture, and assets/mypack/ is where your own new art goes.

Under the Hood (skippable) This is the same trick you learned in Chapter 7: vanilla is just a pack. The game’s own textures, models, and sounds live in a built-in resource pack under the minecraft namespace. When you put a file at the same path under assets/minecraft/ in your pack, yours loads on top and replaces the built-in one. New content under assets/mypack/ simply adds rather than replaces.

Now make your namespace folder and the standard art subfolders inside it. Your tree should look like this:

myassets/
  pack.mcmeta
  assets/
    mypack/
      textures/
      models/
      items/
      lang/
      sounds/

Each of those subfolders has a defined job. Here are the ones a data-pack author cares about, taken straight from the wiki’s resource pack directory structure:

  • textures/.png image files “used as textures for blocks, items, mobs, etc.” This is the raw pixel art.
  • models/.json files “defining three-dimensional shapes used to render blocks and items.”
  • items/.json files “controlling the rendering of items.” This is the modern item-model layer that decides which model an item shows (you’ll meet it properly in Chapter 29).
  • lang/<language code>.json files “containing translations of text.” This is how you give custom names their wording (Chapter 30).
  • sounds/.ogg audio files “that provide audio such as music and sound effects.” Custom sounds also need a sounds.json file, which you’ll meet in Chapter 30.

The wiki lists several more subfolders too: blockstates/ (which model each block state uses), font/ (font providers), atlases/, particles/, and others. You won’t need those for the content packs this book builds, so we’ll leave them named-but-unopened.

Try It! You don’t have to create all of these folders, or any of them: empty folders hold no art and the game ignores them. But making them now gives you labeled drawers to drop files into when Chapters 29 and 30 ask you to. Think of this step as setting out the empty shelves.

Notice the names are singular for some folders and not others: textures, models, lang, sounds, but items. Copy the spelling from the tree above exactly; resource packs are just as picky about folder names as data packs are (remember the singular function/recipe lesson from Chapter 9).

Bundling the resource pack with your world

You now have a complete (if empty) resource pack. The last job is getting Minecraft to use it. There are a few ways to load a resource pack, and the simplest one for a pack that travels with a specific world is to bundle it into the world itself.

The wiki calls this a preloaded resource pack: “A ZIP archive resource pack can be bundled with a world by placing it in the world directory under the name resources.zip. When playing the world, that resource pack appears as the default pack, right above the default resource pack.” In plain terms: zip up your resource pack, rename the zip to exactly resources.zip, and drop it in your test world’s folder. From then on, anyone who plays that world gets your pack automatically.

Here’s the step-by-step:

  1. Zip the contents of myassets. The zip must contain pack.mcmeta and the assets/ folder at its top level, not a myassets/ folder wrapping them. (This is the same gotcha as zipping a data pack: the marker file has to be at the root of the zip.)
  2. Rename the zip to resources.zip, that exact name, all lowercase.
  3. Put it in your test world’s directory, right next to the world’s datapacks/ folder. That’s the same saves/<your world>/ folder you’ve been dropping data packs into since Chapter 9.
  4. Open the world. Minecraft offers the bundled pack; accept it, and your resource pack is live.

saves/<your test world>/resources.zip

Modern Minecraft One quirk the wiki flags: a preloaded resources.zip “is, however, not distributed to other players connecting via LAN.” It loads for you and travels with the world file, but it isn’t auto-pushed to friends who join your LAN game — they’d need their own copy. For solo testing, which is all you need here, that doesn’t matter.

If you’d rather not zip anything while you’re still editing, there’s a second route through the menu, and it’s the one to understand because it reveals an important difference from data packs.

Resource packs have their own enable list

Data packs are turned on with the /datapack command and the data pack list. Resource packs have a completely separate menu instead. The wiki describes it: resource packs are managed “from the options, where they can be moved between ‘Available’ (disabled) and ‘Selected’ (enabled), and reordered.” That’s the Select Resource Packs screen, reached from Options → Resource Packs in the main menu (or by dropping a pack folder/zip onto that screen, which copies it in for you).

Figure (to be captured). the Select Resource Packs screen, with the world’s bundled pack showing in the “Selected” column on the right

This is the single most common point of confusion when you start, so let’s state it flatly: enabling a data pack does nothing to your resource pack, and vice versa. They are two separate on/off lists. If your custom art isn’t showing up, the first thing to check is that the resource pack is enabled in the resource pack menu, not the data pack list.

Under the Hood (skippable) The order matters on that Selected list. The wiki notes packs “load their assets based on the order they appear in… The bottom-most pack loads first, then each pack above it replaces or merges loaded assets with ones it contains.” So a pack higher in the list wins when two packs touch the same file. The vanilla default pack sits at the very bottom, which is why your pack (placed above it) overrides vanilla art rather than the other way around.

Practice

These extend the myassets pack you just built. None of them require drawing a texture yet.

  1. Give your pack an icon. Find or make a small square .png image (16×16 or 64×64 is fine), name it exactly pack.png, and place it in myassets/ next to pack.mcmeta. Re-open the resource pack menu and confirm your image now shows beside the pack’s name.

  2. Add a second namespace. Make a folder assets/spooky/ alongside assets/mypack/, with an empty textures/ inside it. This proves the rule from the wiki that “more than one directory for different namespaces may exist under the assets directory.” You won’t use it yet, but it shows the assets/ folder is happy to hold art for several namespaces at once, handy when a pack grows.

  3. Rewrite the description and reload. Change the description in pack.mcmeta to something with a bit of personality, save, and re-open the resource pack menu (or press the reload-textures key). Hover the pack and confirm your new description appears. This is the resource-pack twin of the /say test you did in Chapter 9: proof the marker file is being read.

Try It! Since the marker file is identical in shape to your data pack’s, open both pack.mcmeta files side by side: mypack/pack.mcmeta and myassets/pack.mcmeta. The only difference is the description text. That sameness is the whole lesson of this chapter: one marker file, two kinds of pack.

What Can Go Wrong

My resource pack doesn’t appear in the menu at all. Almost always this means the pack.mcmeta isn’t where the game expects it. Remember it is “the only mandatory file.” If it’s missing, misnamed (it’s pack.mcmeta, not pack.mcmeta.txt and not packmeta), or buried one folder too deep, the game doesn’t recognize the folder as a pack. If you zipped it, make sure pack.mcmeta is at the top level of the zip, not inside a wrapper folder.

The pack appears but shows an “incompatible” warning. That’s the format number. The wiki explains: “If the format number in a pack’s pack.mcmeta file is higher than the one the game supports, the pack appears as ‘incompatible.’” You’re running a different Minecraft version than your min_format/max_format is built for. Check your game’s actual pack format (press F3 + V in-game to read it) and set min_format/max_format to match. We used 88 for 1.21.9+, so an older game wants a lower number.

I enabled my data pack but my custom look still isn’t showing. This is the separate-lists trap. Turning on a data pack with /datapack does nothing for your resource pack. Open Options → Resource Packs and make sure myassets is in the Selected column, not just sitting in Available. Appearance and behavior live in two different on/off menus.

What You Know Now

You’ve crossed into Part VIII, the visual side of data pack work. You now know that a resource pack is the appearance-and-sound twin of a data pack: same kind of pack.mcmeta marker file (with the same min_format/max_format mechanism from Chapter 9), but an assets/ folder instead of data/. You can lay out assets/<namespace>/ and name what each standard subfolder is for: textures/, models/, items/, lang/, sounds/. You can bundle a pack with a world as resources.zip, and you know resource packs are enabled in their own separate menu, not the data pack list.

You can now build: an empty-but-valid resource pack that loads alongside your data pack, ready to be filled. In Chapter 29 you’ll drop a custom item model and texture into the models/, items/, and textures/ folders you just made. In Chapter 30 you’ll add custom sounds (sounds/ + sounds.json) and custom wording (lang/). The shelves are built; next we stock them.

Chapter 29 — Custom Item Models and Textures

What You’ll Build

Back in Chapter 22 you wrote a sword that pointed at a custom look with the item_model component, and you packed data into a custom_model_data compound, but the artwork the pointer aimed at didn’t exist yet. This is the chapter where you build it.

By the end you’ll have a flame sword in your resource pack: an item that shows a plain blade most of the time, but swaps to a glowing, flaming blade the moment it carries an enchantment shimmer. You’ll make the picture (a small PNG texture), wrap it in a model, and write the item model definition: the little JSON file that decides, frame by frame, which model the game should draw. You’ll learn the five kinds of model definition and finish by making your sword change its own look based on its components, all without a single command running at play time.

Figure (to be captured). two tooltips side by side — a plain custom sword, and the same sword glowing/flaming once it has an enchantment glint

Concepts

Three folders, three jobs

In Chapter 28 you built a resource pack and learned its assets/<namespace>/ layout. Three of the folders inside it work together to draw a custom item, and keeping them straight is most of the battle:

  • assets/<namespace>/textures/ holds the flat images — .png files in PNG format, which provide the images used as textures for models such as items, blocks and mobs. Item images live in textures/item/.
  • assets/<namespace>/models/ holds the shapes — .json files defining the three-dimensional shapes used to render blocks and items. A model says what form the item takes and which texture it wears.
  • assets/<namespace>/items/ holds the item model definitions — .json files controlling the rendering of items. This is the new system this chapter is about: the file that chooses a model.

So the chain runs: a definition in items/ points at a shape in models/, and that shape wears an image from textures/. One picture, one shape, one chooser.

Modern Minecraft. If you follow an older tutorial, you may see it put everything in the models/ file and use a long overrides list to switch models by custom_model_data. Modern Minecraft splits that job out into the items/ folder you’re learning here. The overrides list is gone: the items/ definition with its model types (below) replaces it. If a video edits an item’s model directly with overrides, it’s describing the old way.

The pointer, from Chapter 22

An item knows which definition to use through the minecraft:item_model component you met in Chapter 22. It is the resource location of the item, which references the item model definition /assets/<namespace>/items/<id> without the .json suffix. So if your sword carries item_model="mypack:flame_sword", the game reads the file assets/mypack/items/flame_sword.json. There’s an important warning here: referencing nonexistent models will cause the missing model to be used, rather than falling back to the item ID’s default model. Spell the path wrong and you get the error model, not your diamond sword back.

The shape of an item model definition

Every file in items/ has the same outer shape. The root object holds:

  • a few optional animation switches (hand_animation_on_swap, oversized_in_gui, swap_animation_scale) we’ll leave at their defaults, and
  • a model field holding one items model object — the actual chooser.

That inner model object always has a type. The possible values for type are: minecraft:model, minecraft:composite, minecraft:condition, minecraft:select, minecraft:range_dispatch, minecraft:empty, minecraft:bundle/selected_item, and minecraft:special. This chapter teaches the first five; they’re the ones you’ll reach for. The last three (an item that draws nothing, the selected stack of a bundle, and “special” hard-coded renders like banners and heads) exist, but we leave them named and move on.

Here’s the simplest possible definition, type: minecraft:model, which renders a plain model from the models directory:

assets/mypack/items/flame_sword.json

{
  "model": {
    "type": "minecraft:model",
    "model": "mypack:item/flame_sword"
  }
}

Read it inside-out. The model field (inner) is the namespaced id of a shape in the models/ folder: it specifies the path to the model file of the item, in the form of a namespaced ID. So mypack:item/flame_sword means assets/mypack/models/item/flame_sword.json. The model object (outer) wraps that and gives it a type. Yes, “model” shows up twice: the outer one is the chooser, the inner one is the shape it chose.

The five model types, at a glance

In plain terms:

  • minecraft:model — draw one fixed model. (You just saw it.)
  • minecraft:composite — “render multiple sub-models in the same space.” Stack several models on top of each other.
  • minecraft:condition — pick between two models based on a yes/no test, like “is this item damaged?”
  • minecraft:select — pick a model from a list of named cases, like “which string is in the item’s data?”
  • minecraft:range_dispatch — pick a model based on a number, like “how damaged, on a scale from 0 to 1?”

The Chapter 22 payoff: custom_model_data ↔ model type

Here’s the connection the last few chapters have been building toward. In Chapter 22 you learned that minecraft:custom_model_data is a compound carrying four lists. It holds a list of values used by items model definitions for model selection and coloring. Each of its four lists feeds one of the model mechanisms you just met:

custom_model_data listfeeds this model typewhat it is
flags (booleans)conditionA list of booleans for the condition model type.
stringsselectA list of strings for the select model type.
floatsrange_dispatchA list of floats for the range_dispatch model type.
colorsmodel (its tints)A list of RGB values for the model model type’s tints.

So the data you stamped onto the item in Chapter 22 and the JSON you’re writing now are two halves of the same machine. The item carries the data; the definition reads it. We’ll wire up one of these linkages in the Practice.

Walkthrough

Step 1 — prove the pipeline with a vanilla model

Before making any art, let’s prove the plumbing works by pointing at a model that already exists. Give yourself a stick that renders as a diamond sword. First the definition:

assets/mypack/items/proof.json

{
  "model": {
    "type": "minecraft:model",
    "model": "minecraft:item/diamond_sword"
  }
}

The inner model is minecraft:item/diamond_sword, a model that ships with the game, so we don’t have to build it. Now hand yourself a stick that uses this definition. Put this in a function in your mypack data pack (commands live in .mcfunction files, no leading /):

data/mypack/function/give_proof.mcfunction

give @s stick[item_model="mypack:proof"]

Reload, run the function, and the stick in your hand looks like a diamond sword. The data pack hands out the item; the resource pack draws it. If you instead see a black-and-purple error cube, the game couldn’t find assets/mypack/items/proof.json, so check the filename and the namespace.

Step 2 — a brand-new texture

Now your own art. A standard item image is 16×16 pixels. You need an image editor that can save a PNG with transparency and edit one pixel at a time. Free options that work well:

  • GIMP or Krita (desktop, full-featured),
  • Aseprite (paid, made for pixel art),
  • any web-based pixel editor (search “online pixel art editor”).

Set the canvas to 16×16, turn the background transparent (so the area around your sword shows the world behind it, not a white box), zoom way in, and draw. Keep colors bold and outlines dark. At 16 pixels there’s no room for subtlety. Save it here:

assets/mypack/textures/item/flame_sword.png

Try It! Make two versions in the same style: flame_sword.png (a plain blade) and flame_sword_glow.png (the same blade with orange flames licking up it). You’ll use both in the Practice to make the sword change look.

Step 3 — a model to wear the texture

A texture is just a flat image; a model is the shape that wears it. The models/ folder holds the three-dimensional shapes used to render blocks and items, and an items model definition’s model field is the namespaced id of one of these files. For a flat item like a sword, the model is tiny: it borrows a standard “flat item” shape and points it at your PNG:

assets/mypack/models/item/flame_sword.json

{
  "parent": "minecraft:item/generated",
  "textures": {
    "layer0": "mypack:item/flame_sword"
  }
}

parent: minecraft:item/generated means “use the game’s standard flat-item shape.” layer0 is the image that shape wears: your assets/mypack/textures/item/flame_sword.png.

Under the Hood (skippable). The full grammar of a models/ file (building custom 3-D shapes out of cuboids, faces, and display transforms) is its own large subject, separate from the items/ definition system this chapter teaches. For flat items you’ll almost always reuse minecraft:item/generated exactly as above and just swap the layer0 texture, so you won’t need that grammar here. When you do want to model a custom 3-D shape from scratch, the wiki’s Model page is the place to go.

Step 4 — point the definition at your model

Now update flame_sword.json from the start of the chapter to use your model instead of a vanilla one:

assets/mypack/items/flame_sword.json

{
  "model": {
    "type": "minecraft:model",
    "model": "mypack:item/flame_sword"
  }
}

And give yourself the sword:

data/mypack/function/give_flame_sword.mcfunction

give @s iron_sword[item_model="mypack:flame_sword"]

You now have a fully custom item: a real iron sword (so it still swings and breaks blocks), wearing your own picture. This is the whole pipeline: image, shape, definition, pointer.

Overriding a vanilla item vs. a new visual

You have two ways to put a custom look in front of a player, and they’re worth telling apart:

  • A new visual, keyed by item_model. This is what you just did. The item keeps its real type (iron_sword) but carries item_model="mypack:flame_sword", so only this item looks custom. Every other iron sword in the world is untouched. This is the safe, normal choice.
  • Overriding a vanilla item. If you name your definition file after a vanilla item (say you create assets/minecraft/items/iron_sword.json in your pack), then the rule that an item’s model is based on the minecraft:item_model component still applies, but every iron sword defaults to that definition. That changes all iron swords for anyone using your pack. Use this only when you really mean “retexture the vanilla item everywhere.”

Tinting a model

A model type of minecraft:model can recolor parts of its texture without you drawing new art, using a tint source. The tints field is an optional list of tint sources to apply to the elements of the rendered model (the first entry applies to tintindex 0, the second to tintindex 1, and so on). Each entry is a tint source object with its own type. The tint source types are: minecraft:constant, minecraft:dye, minecraft:firework, minecraft:grass, minecraft:map_color, minecraft:potion, minecraft:team, and minecraft:custom_model_data.

The simplest is constant, which returns a constant RGB color:

assets/mypack/items/red_blade.json

{
  "model": {
    "type": "minecraft:model",
    "model": "mypack:item/flame_sword",
    "tints": [
      {
        "type": "minecraft:constant",
        "value": 16711680
      }
    ]
  }
}

value is a packed RGB number, the same decimal-color trick you saw with colors in Chapter 22 (16711680 is pure red). For this to do anything, the model’s texture needs a part marked with tintindex 0; untinted parts stay their original color. Elements with no tintindex specified remain untinted.

The most useful tint source for our purposes is minecraft:custom_model_data, which returns a value from the colors list in the minecraft:custom_model_data component. That’s the fourth row of our table: the colors list on the item feeds a model tint. Its shape:

assets/mypack/items/dyeable_blade.json

{
  "model": {
    "type": "minecraft:model",
    "model": "mypack:item/flame_sword",
    "tints": [
      {
        "type": "minecraft:custom_model_data",
        "index": 0,
        "default": 16777215
      }
    ]
  }
}

Both fields are simple: index is the index for the field in colors (default 0), and default is an RGB value used when the item has no color there (16777215 is white). Now one definition can render in any color you stamp into the item’s colors list: no extra art, no extra files.

Conditional models: changing look by component state

This is where item definitions earn their keep. Three of the five types choose a model based on the item’s own state at render time.

condition — a yes/no switch

The condition shape is: a type of minecraft:condition, a property (the yes/no test), and two models, on_true (the items model object used when the property is true) and on_false (used when the property is false). There are many boolean property values; the handy ones for items include:

  • minecraft:damaged — “true if the item is damageable and has been used at least once.”
  • minecraft:broken — “true if the item is damageable and has only one use remaining before breaking.”
  • minecraft:using_item — “true if player is currently using this item.”
  • minecraft:selected — “true if item is selected on a hotbar.”
  • minecraft:has_component — “true if the given component is present on the item” (needs an extra component field naming it).
  • minecraft:custom_model_data — returns the value from the flags list in the minecraft:custom_model_data component (this is the flags row of our table; it takes an optional index).

Here’s a sword that looks battered once it’s been used at all:

assets/mypack/items/worn_blade.json

{
  "model": {
    "type": "minecraft:condition",
    "property": "minecraft:damaged",
    "on_true": {
      "type": "minecraft:model",
      "model": "mypack:item/flame_sword_glow"
    },
    "on_false": {
      "type": "minecraft:model",
      "model": "mypack:item/flame_sword"
    }
  }
}

Notice on_true and on_false each hold a whole items model object, a type: minecraft:model with its own model field. Model definitions nest: a chooser’s branches are themselves choosers (or plain models).

select — pick by a named case

A select renders an items model based on a discrete property. Its shape: a type of minecraft:select, a property, a list of cases, and a fallback. Each case has a when (the value to match against the property; if it’s a list, it will match any value in it) and a model. Watch out: fallback is optional, but the game will render a “missing” error model if it’s not present, so always include one.

The property we care about is minecraft:custom_model_data, which returns the value from the strings list in the minecraft:custom_model_data component, the strings row of our table. So the string you put in the item’s data picks the model:

assets/mypack/items/team_blade.json

{
  "model": {
    "type": "minecraft:select",
    "property": "minecraft:custom_model_data",
    "index": 0,
    "cases": [
      {
        "when": "red",
        "model": {
          "type": "minecraft:model",
          "model": "mypack:item/flame_sword_glow"
        }
      },
      {
        "when": "blue",
        "model": {
          "type": "minecraft:model",
          "model": "mypack:item/flame_sword"
        }
      }
    ],
    "fallback": {
      "type": "minecraft:model",
      "model": "mypack:item/flame_sword"
    }
  }
}

An item carrying custom_model_data={strings:["red"]} draws the glowing blade; ["blue"] draws the plain one; anything else falls back to plain. (index here is the optional field for the custom_model_data property, naming which slot of the strings list to read, default 0.)

range_dispatch — pick by a number

A range_dispatch renders an items model based on a numeric property. It selects the last entry whose threshold is less than or equal to the property value. Its shape: a type of minecraft:range_dispatch, a property, a list of entries (each an object with a threshold float and a model), and an optional fallback. A great property here is minecraft:damage, which returns a value from the minecraft:damage component and, with the default normalize: true, divides it by the minecraft:max_damage component, clamped to 0.0 to 1.0. So 0.0 is a fresh tool and 1.0 is about to break:

assets/mypack/items/wear_stages.json

{
  "model": {
    "type": "minecraft:range_dispatch",
    "property": "minecraft:damage",
    "entries": [
      {
        "threshold": 0.0,
        "model": {
          "type": "minecraft:model",
          "model": "mypack:item/flame_sword"
        }
      },
      {
        "threshold": 0.5,
        "model": {
          "type": "minecraft:model",
          "model": "mypack:item/flame_sword_glow"
        }
      }
    ],
    "fallback": {
      "type": "minecraft:model",
      "model": "mypack:item/flame_sword"
    }
  }
}

When the sword is below half-damaged it shows the plain blade; cross 50% wear and it switches to the glow model. The matching custom_model_data source here would be floats (the custom_model_data numeric property returns the value from the floats list), which is the last row of our table.

Practice — the enchanted flame sword

Goal: a sword that looks plain normally and glows once it carries an enchantment shimmer. In Chapter 22 you learned the enchantment_glint_override component forces that shimmer on. We’ll have the model switch on the presence of that component, using condition with the has_component property (true if the given component is present on the item), with a component field naming which one.

First, the definition:

assets/mypack/items/flame_sword.json

{
  "model": {
    "type": "minecraft:condition",
    "property": "minecraft:has_component",
    "component": "minecraft:enchantment_glint_override",
    "on_true": {
      "type": "minecraft:model",
      "model": "mypack:item/flame_sword_glow"
    },
    "on_false": {
      "type": "minecraft:model",
      "model": "mypack:item/flame_sword"
    }
  }
}

Then two ways to hand out the sword, plain then glowing:

data/mypack/function/give_plain.mcfunction

give @s iron_sword[item_model="mypack:flame_sword"]

data/mypack/function/give_enchanted.mcfunction

give @s iron_sword[item_model="mypack:flame_sword",enchantment_glint_override=true]

The first sword shows flame_sword.png; the second carries the glint component, so has_component is true and the definition draws flame_sword_glow.png instead. The model changed itself, from data alone.

Figure (to be captured). the give_enchanted sword in hand — glint shimmer plus the flaming glow texture

Try It! Rebuild this using the Chapter 22 linkage instead of has_component. Set the condition’s property to minecraft:custom_model_data (which reads the item’s flags list), and give the sword with custom_model_data={flags:[true]}. Same visible result, but now you control the switch with your own data instead of relying on a vanilla component.

What Can Go Wrong

You get a black-and-purple error model. The game couldn’t find the definition the item_model component named. Minecraft is blunt about this: a nonexistent model will cause the missing model to be used, rather than falling back to the item ID’s default model. Check that assets/<namespace>/items/<id>.json exists, that the namespace in item_model matches the folder, and remember the pointer omits the .json suffix: item_model="mypack:flame_sword", not "mypack:flame_sword.json".

A select or range_dispatch shows the error model for some items. You forgot the fallback. It’s optional, but the game will render a “missing” error model if it’s not present, so any item whose value doesn’t match a case/entry has nothing to draw. Always include a fallback.

Your tint does nothing. A tint source only recolors model parts marked with the matching tintindex; elements with no tintindex specified remain untinted. A standard minecraft:item/generated flat item has no tint index unless the model adds one, so a plain custom texture won’t change color from a tint alone.

You changed every iron sword by accident. You put your definition in assets/minecraft/items/iron_sword.json instead of under your own namespace. That overrides the vanilla item for everyone. For a one-off custom look, give your definition its own id under your namespace and point item_model at it.

Chapter 30 — Custom Sounds and Language

What You’ll Build

By the end of this chapter your resource pack will play a sound you chose (your own .ogg audio file) through a sound event you registered yourself, fired by the /playsound command and even by an advancement. You’ll also add a language file, which is how Minecraft turns a short code like item.mypack.power_gem into readable words such as “Power Gem” on every player’s screen, in their own language. Then you’ll wire the two together: a custom advancement that, when a player triggers it, runs a function that plays your sound and shows a styled, translated message. This is the chapter that closes Part VIII, so it leans on the resource pack you built in Chapter 28: the assets/ folder lives there, not in data/.

Concepts

A sound event is a name, not a file

You might expect /playsound to take the name of an audio file. It doesn’t. In Java Edition, /playsound takes a sound event, a registered name like entity.pig.ambient that the sound system looks up to decide which audio file (or files) to actually play. The list that maps sound-event names to audio files is a single file called sounds.json.

From the reference: sounds.json … is a file used by the sound system in resource packs which tells the sound system what sound files to play when a sound event is triggered.” And the /playsound page is blunt about it: the command “strictly uses the events defined in sounds.json … and thus a resource pack adding new sound files must define events for them.”

So to add a custom sound you do two things: drop an audio file into your pack, and register a sound event in sounds.json that points at it. The command never names the file; it names the event.

OGG: the one audio format

Minecraft sounds are OGG Vorbis files, the ones ending in .ogg. The resource pack reference lists the sounds directory as holding .ogg files that provide audio such as music and sound effects for the game.” MP3 and WAV won’t work; the game only reads .ogg. Most free audio editors (Audacity is a common one) can export to .ogg.

Modern Minecraft. This hasn’t changed in years, but it still trips people up: there is no /playsound somefile.ogg. The filename is invisible to commands. Everything goes through the event you register in sounds.json. If you rename your .ogg, you only update sounds.json; the command that plays it never changes.

A language file maps codes to words

Minecraft almost never hard-codes the words you see. Instead it uses a translation key, a short code like block.minecraft.stone, and looks up the actual words in a language file for whatever language the player has selected. From the reference: block.minecraft.stone is the ID of the text used for the name of the stone block, and its translation in the en_us language is Stone.” (en_us is the code for U.S. English.)

A language file is just a JSON object full of "key": "value" pairs. You can invent your own keys for your own items and messages, put the words in a language file, and every player sees the right words for their language, changed in just one place.

Walkthrough

Everything in this chapter goes in your resource pack, the one from Chapter 28. Its top folder is assets/, and inside it is your namespace folder. We’ll keep using the namespace mypack, so paths look like assets/mypack/.... (Remember the slogan from Chapter 7: data packs change behavior; resource packs change appearance, and sound.)

Step 1 — Put an OGG file in your pack

Audio files live in a sounds folder inside your namespace. Pick or make a short .ogg clip (say a little chime) and save it here:

assets/mypack/sounds/power_gem_chime.ogg

That’s the whole file-placement rule from the reference: the sounds/ directory under your namespace holds the .ogg files. You can make subfolders if you like (the path just uses forward slashes), but one file in sounds/ is enough to start.

What Went Wrong? If your audio won’t play and it’s a .wav or .mp3 you renamed to .ogg, that’s the problem: renaming doesn’t convert it. Re-export it as a real OGG Vorbis file from an audio editor.

Step 2 — Register a sound event in sounds.json

Now tell the sound system that this file exists, by giving it an event name. The sounds.json file sits directly under your namespace folder:

assets/mypack/sounds.json

{
  "power_gem_chime": {
    "sounds": [
      "mypack:power_gem_chime"
    ]
  }
}

Read this against the reference structure. The root is an object. Each key is a Sound Event, here power_gem_chime. Its value is an object, and the important field is sounds, “the sound files this sound event uses,” a list. Each entry in the list is a path “from the namespace/sounds folder (excluding the .ogg file extension).” So mypack:power_gem_chime means: look in mypack’s sounds/ folder for power_gem_chime.ogg.

Two details the reference is strict about:

  • No .ogg in the path, and use forward slashes (mob/cat/purr, never backslashes).
  • The namespace. A bare path like "power_gem_chime" “defaults to minecraft, so it would look in the vanilla pack and fail. Prepend your namespace and a colon ("mypack:power_gem_chime") so it looks in your pack. This is the single most common custom-sound mistake.

There’s one more namespace rule for the event name itself. The event you just made is fully named mypack:power_gem_chime, because (per the reference) “to get a different namespace than minecraft the file must be under a different namespace.” Your sounds.json is under assets/mypack/, so every event in it automatically belongs to the mypack namespace. You do not write mypack: in front of the key inside the file; the folder already supplies it.

Step 3 — Adding fields: volume, pitch, subtitle, weight

The simple string form is enough for most sounds. But an entry can also be an object when you want to tune it. Here’s a fuller version:

assets/mypack/sounds.json

{
  "power_gem_chime": {
    "subtitle": "subtitles.mypack.power_gem_chime",
    "sounds": [
      {
        "name": "mypack:power_gem_chime",
        "volume": 1.0,
        "pitch": 1.0,
        "weight": 1,
        "stream": false
      }
    ]
  }
}

Every field here is straight from the reference:

  • subtitle“Translation key for the subtitle of the sound,” the line that shows in the corner when subtitles are turned on. It’s a translation key, so we’ll define it in the language file in Step 5. “If not specified, the sound event is not displayed in closed captions.”
  • name — the same path as the string form (no .ogg, with your namespace).
  • volume“a decimal greater than 0.0 … If undefined, defaults to 1.0.”
  • pitch“Plays the pitch at the specified value … defaults to 1.0, but higher and lower values can be chosen.” Higher is squeakier and faster, lower is deeper and slower.
  • weight“The chance that this sound is selected to play.” It matters only when you list several sounds: a sound with weight 2 is “like placing in the name twice.”
  • stream — leave it false for short effects. The reference recommends true only “for sounds that have a duration longer than a few seconds to avoid lag” (long music).

Under the Hood (skippable). A sound event can list several sounds and the game picks one at random, weighted, each time. That’s why a pig doesn’t make the exact same noise twice in a row: entity.pig.ambient has many sound files behind it. You can do the same: list a few chimes and your gem will sound a little different each pickup.

Under the Hood (skippable). One sharp edge from the reference: an audio file with one channel (mono) plays locationally: it gets quieter as you walk away. A two-channel (stereo) file plays at constant volume no matter where you are, like music. For a sound that should come “from” a place in the world, use a mono file.

Step 4 — Play it with /playsound

You met /playsound back in Chapter 2 with vanilla sounds. The exact same command plays your new event. The game can’t tell the difference, because to it an event is an event. We write it in a function, as always (Chapter 9):

data/mypack/function/play_chime.mcfunction

# Play our custom chime to the nearest player, from the master sound category
playsound mypack:power_gem_chime master @p

The reference syntax is playsound <sound> [<source>] [<targets>] [<pos>] [<volume>] [<pitch>] [<minVolume>]. The first argument, <sound>, “should be a Sound Event defined in sounds.json,” and “resource packs may add their own events to sounds.json; the command successfully plays these.” That’s our mypack:power_gem_chime.

The <source> (master, music, record, weather, block, hostile, neutral, player, ambient, voice, or ui) picks which volume slider in the player’s settings controls it. master is always safe. After that, @p is the target. Reload and run the function (/reload, then /function mypack:play_chime) and you should hear your chime.

Two more from the reference, both optional:

  • <volume> doesn’t make the sound louder above 1; it multiplies the audible range (the radius is 16 blocks at volume 1). Below 1, it does get quieter.
  • <pitch> “values less than 0.5 are equivalent to 0.5,” and the pitch value also changes the duration: lower is slower, higher is faster.

Try It! Stop a long sound early with its sibling command, stopsound <targets> [<source>] [<sound>]. For example, in a function: stopsound @a master mypack:power_gem_chime. With no sound given it “stops all sounds,” and a <source> of * stops every category at once.

Step 5 — Add a language file

Now the words. A language file is named for its language code and lives in the lang folder. The resource pack reference lists lang as holding <language code>.json files containing translations of text.” We’ll do U.S. English, en_us:

assets/mypack/lang/en_us.json

{
  "item.mypack.power_gem": "Power Gem",
  "subtitles.mypack.power_gem_chime": "Gem chimes",
  "mypack.message.gem_found": "You found a %s!"
}

This is exactly the structure the reference gives: “an object containing ID and translation pairs.” The key is a translation key you invent; the value is the words shown when that key is used. Three keys here, doing three different jobs:

  • item.mypack.power_gem — a name for a custom item (more on this below).
  • subtitles.mypack.power_gem_chime — the subtitle we referenced from sounds.json in Step 3. Now that it’s defined, players with subtitles on will see “Gem chimes” when the sound plays.
  • mypack.message.gem_found — a message we’ll show in chat, with a slot in it.

That %s is a placeholder, a slot the game fills in later. The reference: placeholders “to be replaced by other text or numbers later can also be used … using the with of a translatable text component,” written as %s. We’ll fill it next.

Under the Hood (skippable). Naming keys with dots (item.mypack.power_gem) is just a convention to keep them tidy and unique; to the game it’s only a string. Vanilla follows the same pattern (block.minecraft.stone), so copying it keeps your keys from clashing with anyone else’s. The reference also notes language files “are merged with other selected packs,” so you only need to list the keys you add; everything else still comes from vanilla.

Step 6 — Use the translation in a text component

A language file does nothing on its own; something has to use the key. The way you reach it is the translate text component you met in Chapter 5. Instead of writing the words directly, you give the key:

data/mypack/function/announce_gem.mcfunction

# Show the translated message, filling its %s slot with the item's translated name
tellraw @a {"translate": "mypack.message.gem_found", "with": [{"translate": "item.mypack.power_gem"}]}

Here’s the connection, straight from the reference’s description of the translatable content type. The translate field is “a translation identifier, corresponding to the identifiers found in loaded language files.” The with field is “a list of text components to be inserted into slots in the translation text.” Our message "You found a %s!" has one slot; with supplies one component, itself a translate for the gem’s name. A player in English sees “You found a Power Gem!” A player in another language sees the same sentence translated, if they (or you) provided those keys for that language.

The reference even uses the game’s own example to show how slots line up: the built-in English file contains "chat.type.advancement.task": "%s has made the advancement %s", with two %s slots, filled by two components in with (the player, then the advancement). Yours works the same way with one slot.

Two safety nets the reference gives you:

  • fallback“If no corresponding translation can be found, this is used as the translated text.” Add "fallback": "You found a gem!" and even a player whose pack is missing will see readable words instead of the raw key.
  • If there’s no translation and no fallback, “the identifier itself is used”: the player just sees mypack.message.gem_found on screen. Ugly, but a useful clue that a key is missing.

Step 7 — Translated item names (the payoff)

This is why language files matter for items. Recall from Chapter 22 that an item can carry an item_name component. If you set item_name to a translate component, the displayed name comes from your language file:

data/mypack/function/give_gem.mcfunction

# Give a named gem whose name comes from the language file key item.mypack.power_gem
give @s emerald[item_name={"translate":"item.mypack.power_gem"}]

Now the item shows as “Power Gem”, and to a French player whose pack defines item.mypack.power_gem as "Gemme de puissance", it shows in French automatically. That’s the whole point of routing names through a language file instead of typing them in: one item, correct in every language, changed in one place.

Practice — a sound that plays when your advancement triggers

Let’s wire all of Part VIII’s threads together: an advancement (Chapter 19) that, when a player earns it, runs a function that plays your custom sound and shows your translated message.

First the function the advancement will launch:

data/mypack/function/gem_reward.mcfunction

# Plays the custom chime and announces it — runs as/at the player who earned the advancement
playsound mypack:power_gem_chime master @s
tellraw @s {"translate": "mypack.message.gem_found", "with": [{"translate": "item.mypack.power_gem"}], "fallback": "You found a gem!"}

Now the advancement that runs it. We’ll keep it simple (it triggers the first time the player picks up, or otherwise gains, an emerald) and we hide its display so it is a pure event detector (Chapter 19). The key piece is the rewards.function field, which the advancement reference confirms runs a function when the advancement is granted:

data/mypack/advancement/found_gem.json

{
  "criteria": {
    "got_emerald": {
      "trigger": "minecraft:inventory_changed",
      "conditions": {
        "items": [
          {
            "items": [
              "minecraft:emerald"
            ]
          }
        ]
      }
    }
  },
  "rewards": {
    "function": "mypack:gem_reward"
  }
}

(The doubled items looks odd but matches the reference exactly: the outer items is a list of item checks, and inside each one the inner items is a list of allowed item IDs, here just minecraft:emerald.)

Recall from Chapter 19 that the reward function runs as the player who earned the advancement and at their position, so inside gem_reward, @s is that player. That’s why we used @s for both the playsound and the tellraw: the sound plays to them, and the message goes to them. Load both your data pack and resource pack, pick up an emerald in a fresh test world, and you should hear your chime and see “You found a Power Gem!”

Try It! Add a subtitle payoff: with subtitles turned on (Options → Music & Sound → Show Subtitles), the subtitles.mypack.power_gem_chime key you defined makes “Gem chimes” appear in the corner each time the sound fires, no extra command needed.

Try It! Make the gem sound a little different each time. Put two or three short chime .ogg files in sounds/, list all of them in the event’s sounds array, and give the rare one a low weight. The game will pick one at random, weighted, every pickup.

What Can Go Wrong

  • The whole pack goes silent and vanilla sounds come back. The sounds reference warns that a single bad entry can make “the entire sounds.json being ignored, in favor of vanilla sounds.” The usual cause is a sound path that doesn’t resolve (wrong namespace, a stray .ogg on the end, a backslash) or a volume/pitch of 0. Fix the one broken entry and the rest come back.

  • /playsound says nobody could hear it. From the reference, the command fails when the target is out of range and minVolume is 0, or when the named event simply doesn’t exist. Double-check the event name matches the key in sounds.json exactly, including the mypack: namespace. Remember a bare name looks in minecraft, not your pack.

  • The screen shows the raw key, like item.mypack.power_gem, instead of words. That’s the reference’s “the identifier itself is used as the translated text”: your lang file is missing the key, or the resource pack isn’t actually loaded, or the player’s language isn’t en_us and you only wrote en_us.json. Add a fallback so a miss degrades to readable words instead of a code.

What You Know Now — Part VIII recap

Part VIII gave appearance to the behavior you built in Parts I–VI. You can now:

  • Build a resource pack and bundle it with your data pack (Chapter 28).
  • Give items custom models and textures, including models that change with the item’s state (Chapter 29).
  • Register custom sound events in sounds.json, drop .ogg audio into sounds/, and play them with /playsound, even from an advancement reward (Chapter 30).
  • Route every name and message through a language file, so your pack reads correctly in any language and you change wording in exactly one place.

You can now build a content pack that does new things and looks and sounds like its own thing, which is exactly what Chapter 34’s complete content-pack project will pull together.

Chapter 31 — Project: Custom Mob Drops and Behavior

What You’ll Build

This is the first project chapter, and it works differently from everything before it. Up to now each chapter taught you one new system (loot tables, predicates, advancements, components) one at a time, each with its own little demo. A project doesn’t teach a new system. It hands you the ones you already have and asks the real question: how do they fit together into one finished thing?

By the end of this chapter your mypack pack (the one you started in Chapter 9) will do all of this, working as a single feature:

  • A husk (the dried-out, desert version of a zombie) that is killed by a player, with a diamond sword, at night has a small chance to drop a one-of-a-kind sword called the Sunforged Blade: enchanted, custom-named, lore-bearing, and secretly tagged so your pack can recognise it later.
  • The moment that special kill happens, a hidden advancement notices it and fires a celebration function (a burst of particles, a sound, and a short status effect on the player) so the rare drop feels rare.
  • And you’ll learn a testing workflow: a repeatable way to force the drop, isolate each condition, and find which piece is broken when something doesn’t fire. In a multi-file project, something always doesn’t fire the first time.

Here is the whole project laid out as files, so you can see the shape before we build it:

mypack/
  data/
    mypack/
      predicate/
        with_diamond_sword.json     (Piece 1 — "killed with a diamond sword?")
        is_night.json               (Piece 1 — "is it night?")
      advancement/
        slew_a_husk.json            (Piece 4 — silent detector for the special kill)
      function/
        husk_reward.mcfunction      (Piece 5 — the particle/sound/effect celebration)
        give_test_blade.mcfunction  (testing helper)
    minecraft/
      loot_table/
        entities/
          husk.json                 (Piece 3 — the gated drop + the custom item)

Six files, each small, each pointing at the others. Every condition, every function, every component here is one you met in Chapters 16 through 23. The skill this chapter builds is assembly: wiring separate files into one feature, and testing it methodically, like a builder.

This chapter extends the mypack pack from Chapter 9 and uses the test world from Chapter 1.

Designing the project before you build it

Good data-pack projects start on paper (or in your head), not in a JSON file. Before writing anything, it helps to name the pieces and the connections between them, because the connections are where projects break.

Here’s the design for our husk drop, written as plain sentences:

  1. When a husk dies, if a player landed the kill, and if that player held a diamond sword, and if it’s night, then (rarely) drop a special sword.
  2. That special sword is a diamond sword that’s been enchanted, renamed “Sunforged Blade,” given a line of lore, and stamped with a hidden tag so the pack knows it’s ours.
  3. Separately, whenever a player kills a husk, run a short celebration (particles, a sound, a buff) so the event has some flair.

Now map each sentence to a system you already know:

Design sentenceThe system that does itChapter
“if a player landed the kill”loot condition killed_by_player17
“rarely”loot condition random_chance17
“if held a diamond sword”a predicate (match_tool), referenced from the loot table17, 18
“if it’s night”a predicate (time_check), referenced from the loot table18
“drop a special sword”a loot entry with loot functions16, 17
“enchanted / renamed / lore / hidden tag”components, set by loot functions / set_components17, 21–24
“drop only happens on the husk”override the husk’s vanilla loot table17
“notice the kill”an advancement with player_killed_entity, no display19
“run a celebration”a function of particle/sound/effect commands9

Every row points back to something you’ve done. The project is just connecting the rows. Let’s build them in dependency order: the small reusable pieces first, then the things that reference them.

Why the husk? A husk is a mob, and that matters. Back in Chapter 17 you learned about loot context (the bundle of facts a loot situation provides). A living entity’s death supplies an attacking_player entity (and a damage source), which is exactly what killed_by_player and the Looting bonus read. A chest opening has no killer, so those conditions would always fail there. Building this on a mob’s death table is what gives our player-kill and weapon checks the context they need.

Piece 1 — the reusable predicates

Two of our conditions, “with a diamond sword” and “at night,” are exactly the kind of test Chapter 18 taught you to save as a predicate file so it can be reused by name. We’ll write them first, because the loot table will reference both.

“Killed with a diamond sword” — match_tool

Chapter 18 introduced match_tool: it checks the tool used to mine the block and, for a kill, that means the weapon the killer was holding. Its single field is predicate, an item test that uses the same structure as advancements, and the advancement item condition (Chapter 19) has an items field that is a list of item IDs the held item must match. So:

mypack/data/mypack/predicate/with_diamond_sword.json

{
  "condition": "minecraft:match_tool",
  "predicate": {
    "items": ["minecraft:diamond_sword"]
  }
}

This is a complete, working predicate, the same file you’d have written in Chapter 18. It passes when the tool involved is a diamond sword and fails otherwise. Saved as a file, the loot table can pull it in by name instead of spelling the check out inline.

Under the Hood (skippable). Remember from Chapter 18 that match_tool “requires tool provided by loot context, and always fails if not provided.” On a mob death table the killer’s weapon is in the context, so the check works. If you ever reused this predicate somewhere with no tool (a bare execute if predicate standing in open air) it would simply fail. That’s the context rule from Chapter 17 doing its job.

“Is it night” — time_check

Chapter 18 also covered time_check: it compares the current day time against given values, takes a value (a number or a min/max range) and an optional period, and is invokable from any context. A Minecraft day is 24,000 ticks (Chapter 7); night runs roughly from 13,000 to 23,000. Setting period to 24000 causes the checked time to be equal to the current daytime, so the comparison resets each day instead of climbing forever:

mypack/data/mypack/predicate/is_night.json

{
  "condition": "minecraft:time_check",
  "value": {
    "min": 13000,
    "max": 23000
  },
  "period": 24000
}

This passes during the night portion of each day. Same shape as the is_daytime predicate from Chapter 18; only the numbers changed.

Try It! Want the blade to drop only during a thunderstorm instead of at night? Swap this predicate’s reference (coming up in Piece 3) for a weather_check predicate with "thundering": true, exactly like the is_thundering file you wrote in Chapter 18’s practice. The loot table doesn’t care which predicate it references, only that it returns pass or fail.

Piece 2 — the rare custom item

Before we make the husk drop the Sunforged Blade, let’s build the blade by hand with a /give command, the way Chapter 21 taught. Building it as a command first means you can hold the finished item in your hand and confirm it looks right before you bury it inside a loot table: a debugging habit worth keeping.

From Chapters 21–24, an item is its ID plus a bag of components written in square brackets: item_id[component=value, component2=value]. Our blade uses five components you already know:

  • custom_name — a text component (Chapter 5) for the item’s name. This component has highest priority to display as the item’s name, and appears italic unless overridden by the text component format, so we set italic:false to keep it upright.
  • lore — “List of additional lines to display in this item’s tooltip,” each line a text component.
  • enchantments — “a map of each of this item’s enchantments to its enchantment level.”
  • rarity — sets the rarity of this item, which affects the default color of its name. It can be common, uncommon, rare, or epic. Note the word default: epic would tint the name light purple on its own, but our explicit custom_name color (gold) wins, since custom_name has highest priority to display as the item’s name. So rarity here mostly sets the tooltip’s rarity tint, not the visible name color. (The light-purple/aqua name color actually shows up through the enchantments component, since an enchanted item’s name is colored by its rarity, so rarity and enchantments work together on the tint; the custom_name color overrides whatever they’d pick.)
  • custom_data — “key-value pairs of any custom data not used by the game.” This is the hidden stamp from Chapter 24: the game ignores it, but your pack can test for it later.

Open your test world’s chat bar and run this as one line:

/give @s diamond_sword[custom_name={"text":"Sunforged Blade","color":"gold","italic":false},lore=[{"text":"Forged in desert light","color":"gray","italic":true}],enchantments={"minecraft:sharpness":4,"minecraft:fire_aspect":2},rarity="epic",custom_data={mypack_sunforged:true}]

You should get a gold-named diamond sword (the custom_name color wins) with a grey lore line, Sharpness IV and Fire Aspect II already on it, the “epic” rarity showing in its tooltip, and, invisibly, the tag {mypack_sunforged:true} riding along in custom_data. Hold it, hover it, swing it. This is the exact item the husk will drop.

Figure (to be captured). the “Sunforged Blade” diamond sword held in hand, tooltip showing the gold name, grey lore line, and the Sharpness IV / Fire Aspect II enchantment lines

Modern Minecraft. That custom_data stamp is how modern packs mark “this is our special item.” Old tutorials detected custom items by matching their name, which is fragile, because a player could rename anything. The supported way (Chapter 24) is a custom_data tag the game never touches and only your pack reads. We won’t read it back in this chapter, but stamping it now means a later project can ask “is the player holding a Sunforged Blade?” with a predicate, and get a reliable yes/no.

Translating the item into loot functions

A loot table can’t drop a finished [...] item directly; it drops a plain item and then modifies it with loot functions (Chapter 17). Each component above maps to a function:

  • custom_name → the set_name function (“Adds or changes the item’s custom name”; field name).
  • lore → the set_lore function (“Adds or changes the item’s lore”; field lore, plus a mode).
  • enchantments → the enchant_with_levels function from Chapter 17 or, for an exact set of enchantments, the set_components function. We’ll use set_components so the blade always comes out with exactly Sharpness IV and Fire Aspect II, not a random enchant.
  • rarity and custom_data → also set_components, the function whose field components is a map of component ID to component value: the loot-table doorway to any component, including the two that have no friendly dedicated function.

We’ll put enchantments, rarity, and custom_data together into a single set_components, and keep set_name and set_lore as their own friendly functions. That’s the same toolkit from Chapter 17; we’re just using set_components for the components that need it.

Piece 3 — the loot table that drops it

Now the centrepiece: override the husk’s loot table so that, on top of its normal drops, a player-killed husk has a small chance to drop the Sunforged Blade, but only with a diamond sword, only at night.

As in Chapter 17, you override a vanilla mob’s drops by writing a file at the same address in the minecraft namespace. The husk’s table lives at entities/husk, so your file is:

mypack/data/minecraft/loot_table/entities/husk.json

Remember the warning from Chapter 17: overriding replaces the whole table, so you’re now responsible for the husk’s normal drops too. We keep a rotten-flesh pool first, then add the gated sword pool. Here is the complete file:

mypack/data/minecraft/loot_table/entities/husk.json

{
  "type": "minecraft:entity",
  "pools": [
    {
      "rolls": 1,
      "entries": [
        {
          "type": "minecraft:item",
          "name": "minecraft:rotten_flesh",
          "functions": [
            { "function": "minecraft:set_count", "count": { "min": 0, "max": 2 } }
          ]
        }
      ]
    },
    {
      "rolls": 1,
      "conditions": [
        { "condition": "minecraft:killed_by_player" },
        { "condition": "minecraft:random_chance", "chance": 0.05 },
        { "condition": "minecraft:reference", "name": "mypack:with_diamond_sword" },
        { "condition": "minecraft:reference", "name": "mypack:is_night" }
      ],
      "entries": [
        {
          "type": "minecraft:item",
          "name": "minecraft:diamond_sword",
          "functions": [
            {
              "function": "minecraft:set_name",
              "name": { "text": "Sunforged Blade", "color": "gold", "italic": false }
            },
            {
              "function": "minecraft:set_lore",
              "mode": "replace_all",
              "lore": [
                { "text": "Forged in desert light", "color": "gray", "italic": true }
              ]
            },
            {
              "function": "minecraft:set_components",
              "components": {
                "minecraft:enchantments": { "minecraft:sharpness": 4, "minecraft:fire_aspect": 2 },
                "minecraft:rarity": "epic",
                "minecraft:custom_data": { "mypack_sunforged": true }
              }
            },
            {
              "function": "minecraft:enchanted_count_increase",
              "enchantment": "minecraft:looting",
              "count": { "min": 0, "max": 1 }
            }
          ]
        }
      ]
    }
  ]
}

Read it the way Chapter 17 taught. "type": "minecraft:entity" declares the mob-death loot context. The first pool is plain Chapter-16 material (rotten flesh, 0–2 of it) so the husk’s normal drop survives the override.

The second pool is the project. Its conditions list holds four tests, and all must pass for the pool to run:

  1. killed_by_player — a player landed the kill (Chapter 17).
  2. random_chance with chance: 0.05 — the 1-in-20 rarity (Chapter 17).
  3. reference to mypack:with_diamond_sword — our Piece 1 predicate. Chapter 18 taught the reference condition: it “invokes a predicate file and returns its result,” with the predicate’s name in the name field. This is the same vocabulary the loot table’s conditions list already speaks: a condition in that list is a predicate, so referencing a predicate file is natural.
  4. reference to mypack:is_night — our second Piece 1 predicate.

That’s the heart of the assembly: two of the four conditions live in their own files and are pulled in by name. If you later decide “actually, a netherite sword should count too,” you edit with_diamond_sword.json once and this table updates automatically. That’s the payoff Chapter 18 promised.

Inside the pool, the single entry drops a plain minecraft:diamond_sword, then its functions reshape it in order (Chapter 17): set_name gives the gold “Sunforged Blade”; set_lore with mode: "replace_all" sets the grey lore line; set_components stamps on the exact enchantments, the epic rarity, and the hidden custom_data tag in one go; and enchanted_count_increase adds the small Looting bonus. The finished drop is exactly the item you /give-tested in Piece 2.

What Can Go Wrong? The path must be exactly data/minecraft/loot_table/entities/husk.json: the minecraft namespace (you’re overriding vanilla), and the singular loot_table folder, the same singular-folder rule that bites people on function and recipe. A typo in the path means your file just sits there, ignored, and husks drop vanilla loot as if nothing changed.

Piece 4 — the silent kill detector

The loot table handles the drop. The celebration needs to know the moment a player kills a husk, and that’s exactly the job of a hidden advancement from Chapter 19.

Recall the pattern: an advancement is really an event listener; strip its display and it becomes a silent detector that just watches for an event and fires a reward function. The event here is player_killed_entity, narrowed to husks. And because we want it to fire on every husk kill, the reward function will revoke the advancement to re-arm it: the loop from Chapter 19.

mypack/data/mypack/advancement/slew_a_husk.json

{
  "criteria": {
    "killed_husk": {
      "trigger": "minecraft:player_killed_entity",
      "conditions": {
        "entity": {
          "type": "minecraft:husk"
        }
      }
    }
  },
  "rewards": {
    "function": "mypack:husk_reward"
  }
}

Notice there is no display field at all: no toast, no screen entry, nothing visible. This is the pure-detector form from Chapter 19. The criteria block has one criterion, killed_husk, whose trigger is player_killed_entity and whose conditions narrow it with an entity whose type is minecraft:husk, copied from the same advancement-entity-condition shape you used in Chapter 19. When a player kills a husk, the criterion completes, the advancement completes, and its rewards.function runs mypack:husk_reward as and at that player.

That as/at detail is what makes the next piece easy. Inside the reward function, @s is the killing player and ~ ~ ~ is where they are.

Piece 5 — the celebration function

Now the payoff: a function that plays a little burst of feedback. It runs as the player who got the kill, so @s and ~ ~ ~ already point at the right person and place. We’ll use particle and playsound from Chapter 2, plus a status effect, now with their full syntax confirmed from the command pages, and then the revoke line from Chapter 19 that re-arms the detector.

mypack/data/mypack/function/husk_reward.mcfunction

particle minecraft:flame ~ ~1 ~ 0.3 0.5 0.3 0.02 30 force
playsound minecraft:entity.blaze.shoot player @s ~ ~ ~ 1 1
effect give @s minecraft:fire_resistance 10 0
advancement revoke @s only mypack:slew_a_husk

Walk each line, all grounded in the command pages:

  1. particle minecraft:flame ~ ~1 ~ 0.3 0.5 0.3 0.02 30 force — the particle command’s full form is particle <name> <pos> <delta> <speed> <count> [force|normal]. So this makes 30 (<count>) flame particles one block above the player (~ ~1 ~), spread within the <delta> box 0.3 0.5 0.3 around that point, with a tiny <speed> of 0.02. When <count> is not 0, the particles are created at random positions scattered around <pos> by <delta>, so you get a little cloud, not a single dot. force makes them show even for players with reduced particle settings.
  2. playsound minecraft:entity.blaze.shoot player @s ~ ~ ~ 1 1 — the playsound command’s form is playsound <sound> <source> <targets> <pos> <volume> <pitch>. The sound must be a sound event defined in sounds.json (for example, entity.pig.ambient); player is one of the eleven source categories from Chapter 2; @s is the killer; 1 1 is full volume and normal pitch.
  3. effect give @s minecraft:fire_resistance 10 0 — the effect command’s form is effect give <targets> <effect> [<seconds>] [<amplifier>]. So this grants Fire Resistance for 10 seconds at amplifier 0. The amplifier rule is explicit: the first tier of a status effect (e.g. Regeneration I) is 0, so 0 means level I.
  4. advancement revoke @s only mypack:slew_a_husk — the re-arm line. From the advancement command page, advancement revoke <targets> only <advancement> “removes a single advancement.” It takes the detector back from this one player so it’s armed again and fires on their next husk kill, the exact loop from Chapter 19.

Figure (to be captured). the moment after a husk dies — a small cloud of flame particles above the player, with the Fire Resistance effect icon just appearing in the HUD

Try It! The chapter wires the celebration to every husk kill. Want it only on the rare drop? That’s harder: the advancement fires on the kill, before knowing whether the loot rolled the sword. One clean approach you already have the tools for: have the loot table’s sword entry also run a small function (you’ll combine loot and functions like this in the projects ahead), or detect the player picking up the Sunforged Blade with an inventory_changed advancement that tests the custom_data stamp. Sketch it; you don’t have to build it yet.

The testing workflow

Here’s the part that separates “I wrote six files” from “I have a working feature.” A multi-file project almost never works on the first /reload, and the worst way to fix it is to stare at all six files at once. Instead, test like a builder: make each piece fail loudly or pass obviously, one at a time.

Step 0 — reload after every edit. Loot tables, predicates, advancements, and functions all hot-reload with /reload (Chapter 7). Get in the habit: edit, save, /reload, test. If /reload prints a red error in chat, a file has a JSON typo; fix that before anything else, because a file that won’t load does nothing.

Step 1 — confirm the item, alone. Before testing drops at all, give yourself the finished blade with a tiny helper function so you’re sure the item is right:

mypack/data/mypack/function/give_test_blade.mcfunction

give @s diamond_sword[custom_name={"text":"Sunforged Blade","color":"gold","italic":false},lore=[{"text":"Forged in desert light","color":"gray","italic":true}],enchantments={"minecraft:sharpness":4,"minecraft:fire_aspect":2},rarity="epic",custom_data={mypack_sunforged:true}]

Run function mypack:give_test_blade (as a chat command, /function mypack:give_test_blade). If the blade looks right here, you know the components are correct; if the loot version later looks different, the bug is in the loot functions, not the item design.

Step 2 — force the drop to confirm the wiring. A 5% chance gated by three conditions is miserable to test by luck. Temporarily make the pool always drop: in husk.json, raise random_chance to 1.0, and comment out (actually, JSON has no comments, so temporarily delete) the two reference conditions and killed_by_player. Now every husk you kill drops the blade. /reload, kill a husk, confirm the sword appears with all its components. This proves the loot entry and functions work, separate from the conditions.

Step 3 — add the conditions back one at a time. Restore killed_by_player first; confirm a player kill still drops it but (say) lava does not. Then restore with_diamond_sword; confirm it drops only when you swing a diamond sword, not your fist. Then is_night; test once at night, once after /time set day. Adding conditions one at a time means that when the drop suddenly stops, you know exactly which condition you just added is the culprit: almost always a typo in a predicate name or a predicate file that itself won’t load. (Test a predicate in isolation the Chapter 18 way: execute if predicate mypack:is_night run say it is night in a function.)

Step 4 — lower the chance and test the detector. Put random_chance back to 0.05. Now test the advancement side independently: kill any husk (the detector ignores the drop and the sword entirely) and confirm the flame burst, the sound, and the Fire Resistance fire every time. If they don’t, the bug is in the advancement or the function, not the loot table.

That four-step loop (item alone → force the drop → conditions one at a time → detector on its own) is the whole testing discipline. Each step isolates one system so a failure points at one file. When all four pass, the project works; restore the real values and play.

What Can Go Wrong? When the whole thing “doesn’t work,” resist editing all six files. Ask which step fails. Item wrong → it’s the components (Step 1). Nothing drops even forced → loot path or JSON error (Step 2). Drops when forced but not normally → a condition/predicate (Step 3). Drop fine but no flair → advancement or function (Step 4). The file map at the top of the chapter is your checklist.

Practice

These extend the project you just built; keep the same files.

  1. A second special drop. Give the husk a second gated pool that drops a different custom item under different conditions: say, a named, custom_data-stamped bone (“Sun-bleached Bone”) when killed during the day instead of at night. Reuse the set_name + set_components pattern, and write a new is_day predicate (or invert is_night with inverted + reference, the Chapter 18 way).

  2. Pick a different mob. Copy husk.json to entities/zombie.json and adapt it so zombies have their own rare drop. Notice how little changes: the loot context is the same for any mob death, so all your conditions and functions carry over. Update the advancement’s entity.type to minecraft:zombie if you want the celebration there too.

  3. Tune the feel. Change the celebration: a different particle (try minecraft:soul or minecraft:crit), a different playsound event, a different effect (a brief minecraft:strength?). Adjust the particle <count> and <delta> and watch how the cloud’s size and density change. This is pure iteration (reload, watch, adjust) and it’s most of what polishing a project actually is.

What Can Go Wrong

The husk drops vanilla loot, like nothing changed. Your override file isn’t being read. Check the path letter for letter: data/minecraft/loot_table/entities/husk.json, minecraft namespace, singular loot_table. Run /reload and watch for a red error: a JSON mistake (a missing comma, a stray bracket) stops the file loading silently as far as gameplay is concerned.

The blade drops, but plain — no name, no enchantments. The entry’s functions aren’t applying. Most often the set_components map has a misspelled component ID, or a function object is missing its "function" key. Use Step 1’s give_test_blade to confirm the target item, then compare it field by field with what actually drops.

It drops when I force it, but never in normal play. A condition is failing. The usual culprit is a reference pointing at a predicate name that doesn’t exist (a typo in with_diamond_sword or is_night), or a predicate file that itself won’t load. Test each predicate alone with execute if predicate (Chapter 18). Remember too that killed_by_player needs a player kill; fall damage, cacti, or another mob won’t count.

The celebration fires but the sword never drops (or vice versa). Good: that’s the design working. The advancement detector and the loot table are independent. The advancement fires on every husk kill; the drop needs all four conditions plus the 5% roll. They’re separate systems wired to the same event, and testing them separately (Steps 2 and 4) is exactly why they don’t get tangled.

What You Know Now

You’ve built your first complete project: six small files (two predicates, a loot table, an advancement, and two functions) wired into a single feature. You saw how a loot table references predicate files by name (reference), how a vanilla mob’s drops are overridden at the minecraft: address while keeping its normal loot, how set_components carries the components (enchantments, rarity, custom_data) that have no friendly loot function, and how a display-less advancement detects an event and fires a celebration function of particle, sound, and effect commands, re-arming itself with advancement revoke so it works on every kill.

Most of all, you learned to test like a builder: isolate the item, force the drop, add conditions one at a time, and check the detector on its own, so when a multi-file project misbehaves, a failure points at one file instead of six. That testing discipline is worth more than any single command in this book; you’ll use it on every project ahead.

You can now build: custom mob drops gated by who/what/when, rare reward items assembled from components inside a loot table, silent event detectors that fire feedback the instant something happens, and (the real skill) a multi-file feature you can actually debug.

Next, in Chapter 32, you’ll turn the advancement from a hidden detector into the star of the show: a whole custom achievement tree with its own themed tab, progressive goals, and polished display, the visible cousin of the silent detector you just wired up.

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:

  1. A theme. Ours is exploration — venturing out, going underground, facing the dragon. The theme decides the icons, titles, and the background art.
  2. 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.
  3. A progression. Children branch rightward from the root, each one linked to a parent, getting harder as you go. We’ll do three tiers:
AdvancementDifficultyHow you earn itFrame
Explorer’s Path (root)given at the start (a tick trigger)task
First Stepseasypick up a maptask
Cave Delvermediumbe deep undergroundgoal
Dragon Slayerhardkill the Ender Dragonchallenge
(secret snack)hiddeneat 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 display is only valid if it includes icon, title, and description (the reference marks all three required once display is present). Leave one out and the whole display is 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 under advancement/ (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 /give with 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

  1. Add a fourth tier. Branch a new advancement off “Cave Delver” (its parent is mypack:explorer/cave_delver) for a different exploration goal (say bred_animals for “Trail Companion”, tame the wild) with its own goal frame and a reward function granting a named lead or saddle. Confirm the new arrow appears in the tab.

  2. 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_block for setting a campfire). Both set parent to mypack:explorer/root. Watch the tab fork into two branches.

  3. A second hidden detector. Copy secret_snack.json to a new display-less file inside explorer/ that watches a different event (for instance player_killed_entity on a minecraft: bat) and whose reward function does something fun and then advancement revoke @s only <its own id>. Confirm it re-fires every time and never shows on the tab.

  4. 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 display is valid, and the root must grant itself or nothing lights up. Make sure the root has icon + title + description and a criterion that actually completes (the tick trigger completes immediately). If the whole tab is missing, the root’s display is probably invalid (a missing required field).

  • “A child floats off on its own / isn’t linked.” Its parent must be the exact id of another advancement in your tree, including the explorer/ subfolder, e.g. mypack:explorer/first_steps, not mypack:first_steps. A wrong or misspelled parent either 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, and lore must be a list [ ... ] of text components even for one line. Re-read Chapter 21’s bracket rules if /give complained.

  • “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.

Chapter 33 — Project: A Simple Minigame

What You’ll Build

This is the big one. Up to now every chapter taught a tool: scoreboards count, storage remembers, /execute reshapes who and where a command runs, /schedule makes things happen later, /title and /tellraw talk to players. In this chapter you compose the ones you already have into a single working game.

The game is King of the Hill: players gather, someone starts a round, a “3… 2… 1… GO!” countdown plays, and then everyone races to stand inside a marked hill region. While you’re on the hill you earn a point every tick; the player with the most points when the round timer runs out wins. The winner is announced, scores reset, and the game returns to waiting for the next round.

By the end you’ll have a complete, self-contained project pack (a brand-new pack named hill_pack with its own namespace hill) that you can drop into any world and play. Along the way you’ll see the single most important idea in data pack design: a game is a state machine. It is always in exactly one phase, it does work according to that phase, and it moves to the next phase when something happens. You met that pattern in Chapter 27 with a tiny three-state demo; here it grows into a real game.

Figure (to be captured). a flat arena with a square of gold blocks (the hill) in the centre, a sidebar scoreboard on the right showing two players’ scores, and an action-bar line reading “Time left: 18”

A note on packs. Everything since Chapter 9 has gone into your mypack pack. This chapter is different: a finished game deserves its own pack, so it can be shared, enabled, and disabled on its own. We’ll build hill_pack from scratch, with its own pack.mcmeta and, importantly, its own minecraft:load and minecraft:tick tags. We are not touching mypack’s canonical load and tick tags. Two packs can each add their own entries to minecraft:load/minecraft:tick; the game runs them all. Keeping the game’s wiring inside the game’s own pack is what makes it a clean, portable project pack.

The architecture: four phases as a state machine

Before writing a single command, let’s design the game on paper. A minigame is a handful of modes, and the game is always in exactly one of them. Ours has four phases:

  • init — runs once, when the pack loads. It builds the scoreboards and parks the game in waiting. (This is the only phase that isn’t a “mode you sit in”; it sets everything up and immediately hands off to waiting.)
  • waiting — the lobby. Players join, nothing is being scored, and the game waits for someone to press start.
  • running — a round is live. Each tick, the game checks who’s on the hill, awards points, updates the on-screen timer, and watches for the round to end.
  • cleanup — the round just ended. Announce the winner, reset the scores and tags, and drop back to waiting for the next round.

The arrows between them, the transitions, are the whole game:

init ──▶ waiting ──(start)──▶ countdown ──▶ running ──(timer ends)──▶ cleanup ──▶ waiting ──▶ …

(We’ll give the 3-2-1 countdown its own short-lived phase, countdown, so that the per-tick logic knows not to score anyone while the numbers are still counting down.)

Here is the key design decision, and it’s exactly the one Chapter 12 drilled into you: **the current phase is game STATE, so it lives in command storage, not in a scoreboard. A scoreboard holds numbers you compare and show players (each player’s points, the round timer). The phase is a named mode, and storage is where named, structured state belongs. Chapter 12’s mnemonic was “scoreboards count; storage remembers.” The board counts the points; storage remembers which phase we’re in. We’ll keep the phase as a string at hill:game, in a compound called Game:

Game: { phase: "waiting" }

Every tick, one function reads that phase and dispatches to the right logic. That dispatcher is the heart of the whole pack, and it has a name: the tick router.

Building the project pack skeleton

Let’s lay the pack out. A pack needs its marker file, pack.mcmeta (Chapter 9). Create the folder hill_pack and give it:

hill_pack/pack.mcmeta

{
  "pack": {
    "description": "King of the Hill minigame",
    "min_format": 88,
    "max_format": 88
  }
}

That’s the same marker shape and the same format number (88) you used for mypack in Chapter 9: a pack is a pack. Next, the game needs to set itself up the moment the pack loads, and it needs one function to run every tick. Those are the two function tags from Chapters 9 and 4, but this time they belong to hill_pack, not mypack.

hill_pack/data/minecraft/tags/function/load.json

{
  "values": [
    "hill:init"
  ]
}

hill_pack/data/minecraft/tags/function/tick.json

{
  "values": [
    "hill:tick"
  ]
}

Modern Minecraft. It might look odd to have a second data/minecraft/tags/function/load.json when mypack already has one. That’s completely fine, and it’s why data packs are built the way they are. The minecraft:load function tag is shared: every enabled pack contributes its own values entries, and the game merges them. hill_pack adds hill:init; mypack still has its own entries; nobody overwrites anybody. (Remember from Chapter 14 that a tag file with no "replace": true extends rather than replaces.) Keeping hill’s wiring inside hill_pack is exactly what makes the game a tidy, shareable unit.

Everything else lives under hill_pack/data/hill/function/. From here on, file paths are written relative to the pack, and every function is named hill:<name>.

Phase 1 — init: setting up the game

The init function runs once on load. It creates the two scoreboards the game needs, shows the points board on the sidebar, and parks the phase in waiting.

hill_pack/data/hill/function/init.mcfunction

# Runs once on load (wired into minecraft:load via hill:init).
# Two objectives: one for player points, one to hold the round timer.
scoreboard objectives add hill_points dummy
scoreboard objectives add hill_timer dummy

# Show the points objective on the right-hand sidebar (Chapter 11).
scoreboard objectives setdisplay sidebar hill_points

# Put the game into its starting phase. Storage holds the state (Chapter 12).
data modify storage hill:game Game.phase set value "waiting"

# Announce that the game is loaded and ready.
tellraw @a {"text":"[King of the Hill] Loaded. Run hill:join to play.","color":"gold"}

Both objectives use the dummy criterion, the plain “I’ll set this myself with commands” kind from Chapter 11, perfect for points and a timer that we control. setdisplay sidebar hill_points puts the points board on the sidebar, the panel on the right edge of the screen (Chapter 11’s display slots). And data modify storage hill:game Game.phase set value "waiting" writes the phase string into storage with the exact /data modify ... set value form you learned in Chapter 12.

Phase 2 — waiting: joining the game

In the lobby, players opt in by running a join function. Joining does three things: it tags the player so the rest of the game can find “who’s playing” (Chapter 13’s runtime entity tags), it zeroes their score for a clean start, and it tells them they’re in.

hill_pack/data/hill/function/join.mcfunction

# A player joins the game. Mark them with a runtime tag (Chapter 13).
tag @s add hill_player

# Start their score at 0 (Chapter 11).
scoreboard players set @s hill_points 0

# Confirm to just this player.
tellraw @s {"text":"You joined King of the Hill! Wait for the round to start.","color":"green"}

tag @s add hill_player is the /tag command from Chapter 13: it sticks the label hill_player on whoever ran the function. Later, @a[tag=hill_player] selects exactly the players who have joined: that’s how the game tells contestants apart from spectators. To play, a player runs /function hill:join (typed in chat with its slash, since they’re calling it by hand).

Try It! Right now players join by typing a command. In Chapter 19 you learned the hidden-advancement-as-detector trick: an advancement with no display that fires a reward function and re-arms itself. You could make “step on the emerald block by the lobby” run hill:join automatically. The game logic below doesn’t change at all; only how hill:join gets called does.

Phase 3 setup — start and the countdown

Starting a round is a transition: it should only work when we’re actually waiting, and it moves the game forward. We guard it with the Chapter 27 trick (an if data test on a compound filter) so that mashing start during a live round does nothing.

hill_pack/data/hill/function/start.mcfunction

# Only start if we are in the waiting phase. The {phase:"waiting"} compound
# filter (Chapter 12/27) makes this line run ONLY when that's the current phase.
execute if data storage hill:game Game{phase:"waiting"} run data modify storage hill:game Game.phase set value "countdown"

# Seed the countdown number (3, 2, 1) on a fake player (Chapter 11).
execute if data storage hill:game Game{phase:"waiting"} run scoreboard players set #count hill_timer 3

# Kick off the self-rescheduling countdown one second from now (Chapter 26).
execute if data storage hill:game Game{phase:"waiting"} run schedule function hill:countdown_tick 20t replace

# Tell everyone a round is starting.
execute if data storage hill:game Game{phase:"waiting"} run tellraw @a {"text":"A round is starting!","color":"yellow"}

Every line is guarded by the same if data storage hill:game Game{phase:"waiting"} so the whole start sequence only fires from the waiting phase. The #count score holder is a fake player (the # prefix hides it from the sidebar, Chapter 11) used to carry the countdown number. We store the number on the hill_timer objective for now since it’s a temporary counter; the real round timer reuses the same objective later. The last new piece is schedule function hill:countdown_tick 20t replace, the Chapter 26 pattern: run a function in 20t (one second; 20 ticks per second from Chapter 7), with replace so starting twice can’t stack two countdowns.

Now the countdown itself. It announces the current number with a big /title, counts down, and either re-schedules itself or, when it reaches zero, flips the phase to running and starts the round timer.

hill_pack/data/hill/function/countdown_tick.mcfunction

# Show the current count big on screen for everyone (Chapter 5 title).
execute if score #count hill_timer matches 1.. run title @a title {"text":"","extra":[{"score":{"name":"#count","objective":"hill_timer"}}]}

# When the count hits 0, show GO! instead of a number.
execute if score #count hill_timer matches 0 run title @a title {"text":"GO!","color":"green"}

# Count down by one.
scoreboard players remove #count hill_timer 1

# If there are still numbers to show, re-schedule for one more second.
execute if score #count hill_timer matches 0.. run schedule function hill:countdown_tick 20t replace

# When the count drops below 0, the countdown is over: BEGIN THE ROUND.
execute if score #count hill_timer matches ..-1 run function hill:begin_round

Read it the same way you read the Chapter 26 countdown: show the number (matches 1.. means “1 or more”), special-case zero as “GO!”, decrement, and re-schedule while matches 0.. (“0 or more”) still holds. The one new line is the last: once the count goes below zero (matches ..-1, meaning “−1 or less”), we call hill:begin_round instead of re-scheduling, which switches the game into its running phase.

hill_pack/data/hill/function/begin_round.mcfunction

# Transition countdown -> running.
data modify storage hill:game Game.phase set value "running"

# Set the round length: 600 ticks = 30 seconds (20 ticks/second, Chapter 7).
scoreboard players set #round hill_timer 600

# Fresh scores for everyone who joined.
function hill:reset_scores

hill:begin_round writes the new phase, sets the round timer to 600 (thirty seconds at 20 ticks a second), held on the fake player #round, and calls a helper to zero every contestant’s points so the round starts fair:

hill_pack/data/hill/function/reset_scores.mcfunction

# Zero the points of every joined player (Chapter 3 selector + Chapter 11).
scoreboard players set @a[tag=hill_player] hill_points 0

@a[tag=hill_player] is the Chapter 3/12 selector: all players carrying the hill_player tag. One command sets every contestant’s score to 0.

Phase 3 — running: the tick router and the round logic

Now the engine. Remember the plan: one function runs every tick (it’s the only entry in our tick.json), and its job is to look at the phase and dispatch. That’s the tick router:

hill_pack/data/hill/function/tick.mcfunction

# THE TICK ROUTER. Runs every tick (wired into minecraft:tick via hill:tick).
# Read the phase from storage and run the matching phase's logic. Only ONE of
# these lines fires per tick, because the game is in exactly one phase.
execute if data storage hill:game Game{phase:"running"} run function hill:run_tick

That’s the whole router for now: a single line, because waiting, countdown, and cleanup don’t need per-tick work (waiting just sits there; the countdown drives itself with /schedule; cleanup runs once and exits). The if data storage hill:game Game{phase:"running"} test means hill:run_tick only runs while a round is live. If you later add per-tick lobby effects, you’d add one more guarded line. The router scales by adding lines, never by getting tangled.

Here’s the per-tick round logic. It does three jobs every tick: award points to whoever is on the hill, update the on-screen timer, and count the round timer down and end the round when it hits zero.

hill_pack/data/hill/function/run_tick.mcfunction

# Runs every tick WHILE the phase is "running" (called by the router).

# 1) AREA DETECTION + SCORING.
#    For each joined player standing inside the hill region, add a point.
#    The hill is a box centred on (0, -60, 0): dx/dy/dz give its size (Chapter 3).
#    +1 each is the per-tick reward; the player who camps the hill longest wins.
execute as @a[tag=hill_player] at @s if entity @s[x=-3,y=-61,z=-3,dx=6,dy=3,dz=6] run scoreboard players add @s hill_points 1

# 2) TIMER UI.
#    Show the round timer on the action bar for everyone (Chapter 5).
title @a actionbar {"text":"Time left: ","extra":[{"score":{"name":"#round","objective":"hill_timer"}}]}

# 3) COUNT THE ROUND TIMER DOWN.
scoreboard players remove #round hill_timer 1

# 4) END THE ROUND when the timer reaches 0: go to cleanup.
execute if score #round hill_timer matches ..0 run function hill:cleanup

There’s a lot of Part I–VI in those five lines, so let’s name each move:

  • Area detection. execute as @a[tag=hill_player] at @s runs the rest as each contestant, standing where they stand (Chapter 4’s as/at). Then if entity @s[x=-3,y=-61,z=-3,dx=6,dy=3,dz=6] is the Chapter 3 volume selector: it asks “is this same player (@s) inside the box whose corner is (-3, -61, -3) and which extends 6 blocks along x, 3 up, and 6 along z?” In plain terms, that’s a 6×6 square three blocks tall: the hill. Only players who pass that test reach the run scoreboard players add @s hill_points 1, so only players on the hill gain a point this tick. (Adjust the numbers to wherever your gold-block hill actually is.)
  • Timer UI. title @a actionbar {...} prints to the action bar, the line just above the hotbar (Chapter 5). The {"score":{"name":"#round","objective":"hill_timer"}} text component prints the live value of the #round timer, the same “score in text” trick from Chapter 11. So everyone sees “Time left: 600”, “Time left: 599”, …, ticking down in real time.
  • Count down. scoreboard players remove #round hill_timer 1 knocks one off the round timer each tick (Chapter 11).
  • End the round. if score #round hill_timer matches ..0 (“0 or below”) fires the moment the timer runs out, calling hill:cleanup to wrap up. Because the router only runs run_tick while the phase is running, and cleanup immediately changes the phase, the round ends exactly once.

Under the Hood (skippable). Notice we never wrote a per-player timer or a for each player loop for the timer: there’s only one round timer, on the fake player #round, shared by everyone. But points are per-player, so they live on the real players via @a[tag=hill_player]. That split is the Chapter 12 decision guide in miniature: one shared number (the clock) versus a number that belongs to each entity (their score). Picking the right holder for each value is most of what makes a game’s data clean.

Score arithmetic: scoreboard players operation

Our scoring uses plain scoreboard players add @s hill_points 1, adding a fixed 1 each tick, which is all King of the Hill needs. But back in Chapter 11 we deferred the other way to change a score: scoreboard players operation, which does math between two scores. This is the chapter that promised to deliver it, so here’s the full tool:

scoreboard players operation <targets> <targetObjective> <operation> <source> <sourceObjective>

It applies an arithmetic operation that alters the targets’ scores in the target objective, using the sources’ scores in the source objective as input. Here is every operator:

  • =assignment: set the target’s score to the source’s score.
  • +=addition: add the source’s score to the target’s.
  • -=subtraction: subtract the source’s score from the target’s.
  • *=multiplication: set the target to the product of the two.
  • /=floor division: divide the target by the source, rounded down to an integer.
  • %=modulus: divide, and keep the positive remainder.
  • ><swap: swap the target’s and source’s scores.
  • <choose minimum: set the target to the source only if the source is smaller.
  • >choose maximum: set the target to the source only if the source is larger.

One rule is worth remembering: in all cases except ><, the source’s score remains unchanged, and if the target or source isn’t tracked by the specified objective, it is set to 0. So all but the swap leave the source alone, and an unset score is treated as 0.

Where would a King of the Hill game use this? Here’s a natural example: a double-points power-up. Suppose you keep a per-player multiplier in an objective hill_mult (1 normally, 2 while a power-up is active). Instead of add ... 1, you could award the player their multiplier each tick:

hill_pack/data/hill/function/score_with_mult.mcfunction

# Award each on-hill player their current multiplier, using operation +=.
# "+= @s hill_mult" adds the player's own multiplier score onto their points.
execute as @a[tag=hill_player] at @s if entity @s[x=-3,y=-61,z=-3,dx=6,dy=3,dz=6] run scoreboard players operation @s hill_points += @s hill_mult

That single operation @s hill_points += @s hill_mult reads “add this player’s hill_mult score onto their hill_points score”: so a player with a 2× multiplier gains 2 per tick, a normal player gains 1 (their multiplier), and a player whose multiplier was never set gains 0 (unset counts as 0). This is optional polish (the base game uses the simple add 1), but operation is the tool the moment you want score math instead of fixed increments.

Try It! Use > (choose maximum) to track a best-ever score across rounds: keep an objective hill_best, and at cleanup run scoreboard players operation @s hill_best > @s hill_points for each player. It copies this round’s points into hill_best only if they beat the old best: a high-score table for free.

Phase 4 — cleanup: declaring a winner and resetting

When the timer hits zero, hill:cleanup runs once. It needs to find the player with the most points, announce them, and reset the game to waiting. Finding “the highest score” is a perfect use of a selector sort from Chapter 3: @a[tag=hill_player] with a descending sort on the hill_points score, limited to one player, is the leader.

hill_pack/data/hill/function/cleanup.mcfunction

# Runs once when the round ends.

# Move the phase out of "running" immediately so run_tick stops firing.
data modify storage hill:game Game.phase set value "cleanup"

# Announce the winner: the joined player with the highest hill_points.
# The selector below picks exactly one (limit=1, highest first); we show
# their name and score (Chapter 5/10).
execute as @a[tag=hill_player,scores={hill_points=1..},limit=1,sort=descending] run tellraw @a [{"text":"Winner: "},{"selector":"@s"},{"text":" with "},{"score":{"name":"@s","objective":"hill_points"}},{"text":" points!","color":"gold"}]

# Big title for everyone.
title @a title {"text":"Round over!","color":"gold"}

# Clear the runtime tag so players must re-join for the next round, and
# return the game to the waiting phase.
tag @a remove hill_player
data modify storage hill:game Game.phase set value "waiting"
tellraw @a {"text":"Run hill:join to play again.","color":"yellow"}

The winner line is the densest one, so unpack it:

  • @a[tag=hill_player,scores={hill_points=1..},limit=1,sort=descending] selects joined players who scored at least 1 (scores={hill_points=1..}, the Chapter 11 score selector), sorts them highest-first (sort=descending), and keeps just the top one (limit=1). That’s the winner.
  • The /tellraw message is a list of text components (Chapter 5): a label, the winner’s name via {"selector":"@s"}, their score via {"score":{"name":"@s","objective":"hill_points"}}, and a gold “points!” tail. Because the line runs as the winner, @s inside the components means the winner.

After the announcement we tag @a remove hill_player to clear everyone’s join tag (a fresh hill:join is needed for the next round) and data modify storage hill:game Game.phase set value "waiting" to park the machine back in the lobby. The state machine has come full circle: waiting → countdown → running → cleanup → waiting, ready to go again.

Figure (to be captured). chat reading “Winner: Steve with 214 points!” in gold, with a “Round over!” title across the screen

What Went Wrong? “Nobody is ever the winner.” The winner selector requires scores={hill_points=1..}, at least one point. If no contestant ever stood on the hill, nobody qualifies and the tellraw simply doesn’t fire (no winner to announce). That’s correct behaviour, not a bug. But if you expected a winner and got none, check that your hill box (x/y/dx/dy/dz) actually lines up with where the gold blocks are.

Wiring it together

The pack is built. Here’s the complete file list for hill_pack, so you can confirm nothing’s missing:

hill_pack/pack.mcmeta
hill_pack/data/minecraft/tags/function/load.json      (values: hill:init)
hill_pack/data/minecraft/tags/function/tick.json      (values: hill:tick)
hill_pack/data/hill/function/init.mcfunction
hill_pack/data/hill/function/join.mcfunction
hill_pack/data/hill/function/start.mcfunction
hill_pack/data/hill/function/countdown_tick.mcfunction
hill_pack/data/hill/function/begin_round.mcfunction
hill_pack/data/hill/function/reset_scores.mcfunction
hill_pack/data/hill/function/tick.mcfunction
hill_pack/data/hill/function/run_tick.mcfunction
hill_pack/data/hill/function/cleanup.mcfunction

To play:

  1. Build a flat arena and place a 6×6 square of gold blocks centred near (0, -60, 0) — that’s the hill. (Use whatever coordinates you like; just match the run_tick box to them.)
  2. Enable hill_pack and /reload. You’ll see the gold “[King of the Hill] Loaded” message, and the sidebar appears. init has set the phase to waiting.
  3. Each player runs /function hill:join.
  4. Someone runs /function hill:start. The 3-2-1 countdown plays for everyone, then “GO!”.
  5. Race to the hill. Stand inside it to rack up points; watch the action-bar timer tick down.
  6. When the timer hits zero, the winner is announced, the game resets, and you can hill:join and hill:start again.

Notice what made this manageable: every phase is a separate, small function, and one router decides which runs. You never wrote a giant tangle of nested ifs. Adding a feature means adding a function and, maybe, one guarded line in the router. That’s the payoff of designing the game as a state machine before writing commands.

Practice

  1. Lobby countdown to auto-start. Right now a round only starts when someone runs hill:start. Add a per-tick lobby check: in the router, add a line that runs a new hill:waiting_tick while Game{phase:"waiting"}. Have waiting_tick count joined players with execute store result score #players hill_timer if entity @a[tag=hill_player] (the Chapter 27 “count entities into a score” pattern) and, once there are 2 or more, call hill:start automatically.

  2. Sudden-death overtime. In cleanup, before declaring a winner, check whether the top two scores are tied. If they are, instead of ending, set the phase back to running and the round timer to a short 200t. (Hint: copy the two top scores into fake players with scoreboard players operation, then compare with if score.)

  3. A win-target instead of a clock. Change the game so the first player to reach 300 points wins immediately, regardless of the timer. Add a line to run_tick: execute as @a[tag=hill_player,scores={hill_points=300..}] run function hill:cleanup. (Make sure cleanup still works when called mid-round — it already moves the phase out of running, so the router stops run_tick cleanly.)

  4. A second hill. Add a second gold square somewhere else and award points for standing on either. Remember from Chapter 27 that “OR” in commands is two separate chains: add a second execute as @a[tag=hill_player] at @s if entity @s[...second box...] run scoreboard players add @s hill_points 1 line. A player on either hill scores.

What Can Go Wrong

What Went Wrong? “The game scores people during the countdown / lobby.” This means run_tick is running when it shouldn’t. The fix is the router: scoring must only happen while Game{phase:"running"}. Confirm tick.json points at hill:tick (the router), not straight at hill:run_tick, and that the router’s line is guarded by if data storage hill:game Game{phase:"running"}. If you wire run_tick directly into the tick tag, it runs in every phase, and you’ll score people in the lobby.

What Went Wrong? “The phase never changes / the game is stuck.” A state machine only advances if something writes the next phase. Trace the transition: does start actually set "countdown"? Does begin_round set "running"? Does cleanup set "waiting"? A common slip is guarding a transition with the wrong phase name: Game{phase:"waiting"} versus a typo like Game{phase:"wating"}. The compound filter is an exact string match, so one wrong letter means the line silently never fires. Read the phase back any time with the Chapter 12 command, typed in chat: /data get storage hill:game Game.phase.

What Went Wrong? “Two countdowns are running at once.” You started a round twice and the schedules stacked. Two defences are already in place: start is guarded so it only fires from waiting (once we’re in countdown, a second start does nothing), and the schedule function hill:countdown_tick 20t replace uses replace (Chapter 26), so even a duplicate schedule overwrites rather than piles up. If you ever change replace to append, expect overlapping countdowns: that’s exactly what append is for, and exactly what you don’t want here. To kill a stuck countdown by hand: /schedule clear hill:countdown_tick (full namespaced ID required, Chapter 26).

Chapter 34 — Project: A Complete Content Pack

What You’ll Build

This is the chapter where everything comes together. Across the last thirty-two chapters you learned the pieces one at a time: components, recipes, loot tables, advancements, models, sounds. Now you’ll put them in one box.

By the end you’ll have a complete content pack of your own: a data pack and a resource pack, shipped as a pair, under one shared name. The theme is Emberforge, a fire-and-forge treasure set. You’ll plan it on paper first, then build three custom items (a glowing Ember Blade, a fast Forgemaster’s Hammer, and an edible Molten Bun), write the recipes that craft them, make a cache loot table that drops them in a chest, build a small advancement tree that guides the player from “you made your first Emberforge item” to “you opened the cache,” and finish with a custom sound that plays at the big moment. Then you’ll test the whole thing, end to end.

Two new ideas only: a content pack (data pack + resource pack as one project) and a design document (a short written plan you make before you build). Everything else is something you already know, used together for the first time.

Content pack. A content pack is a data pack and a resource pack shipped together as one project, so the behavior (the data files) and the look-and-sound (the asset files) arrive as a unit. The two halves are separate things (a data pack is “a collection of data used to configure a number of features,” and a resource pack carries the textures, models and sounds), but a player downloading your work just wants both at once. This chapter builds both.

Figure (to be captured). the finished pack in action — a chest spilling glowing Ember Blades, the advancement toast popping, a forge-themed background

Plan First: The Design Document

Every chapter so far handed you the plan. This time you make it. Before you create a single folder, write a design document: a short, plain plan of what you’re building. It’s a few lines in a text file (or on paper) that answer five questions, no JSON and no code. Doing this first is the single biggest favor you can do yourself, because it turns “I’ll add some cool items” into a finite, finishable list.

Design document. A short written plan made before building: the theme, the list of items, what each item does, where the player finds them, and how they’re obtained. No code, just decisions, so you know when you’re done.

Here is the design document for this project. Yours, for your own pack, will look different. That’s the point.

EMBERFORGE — design document

Theme:     A fire/forge treasure set left behind by a master smith.
Namespace: emberforge   (both packs share this name)

Items:
  1. Ember Blade        — a sword that glows; sharp; "rare" coloring.
  2. Forgemaster's Hammer — a pickaxe that mines fast; bonus attack damage.
  3. Molten Bun         — a food item; restores hunger; quick to eat.

Where found: a forge "cache" chest (a structure — Part XI). For now we ship the
             chest's LOOT TABLE and test it directly.

How obtained: each item is craftable (a recipe), AND drops from the cache chest.

Guiding path (advancements):
  root   "Apprentice"  — get any Emberforge item
  child  "Swordsmith"  — hold the Ember Blade
  hidden "Cache Opener" — get the cache marker; plays a forge sound

Polish: one custom sound, emberforge:cache_open, at the final advancement.

That fits on a screen, and it tells you exactly when the pack is finished: three items, three recipes, one loot table, three advancements, one sound. We’ll build it in that order.

The Project Skeleton

A content pack is two folders sitting next to each other. Each is its own pack with its own marker file: a data pack “is either a folder or a .zip file containing a pack.mcmeta file,” and a resource pack has its own pack.mcmeta and an assets/ folder instead of data/. So your project root holds two folders:

emberforge/                 <- the DATA pack  (behavior; has data/)
emberforge_resources/       <- the RESOURCE pack (look & sound; has assets/)

Both use the same namespace, emberforge, so every path in either pack reads .../emberforge/.... That’s a project namespace: one name shared by both halves, which keeps your items, models and data files lined up. (The reader’s earlier mypack/myassets project did the same thing: same namespace inside, different folder names outside. Here we start a clean project of its own so you practice building one from nothing.)

Project namespace. The single namespace shared by both packs (emberforge). A namespace exists “to avoid files from different packs unintentionally interfering with each other”; sharing one name within your own project is what keeps a data file and its matching asset pointing at each other.

The two marker files

Both packs need a pack.mcmeta. You built one of each back in Chapters 9 and 28, and they use the same min_format/max_format version mechanism. Here they are for this project.

emberforge/pack.mcmeta

{
  "pack": {
    "description": "Emberforge — a forge-master's treasure set (data)",
    "min_format": 88,
    "max_format": 88
  }
}

emberforge_resources/pack.mcmeta

{
  "pack": {
    "description": "Emberforge — a forge-master's treasure set (assets)",
    "min_format": 88,
    "max_format": 88
  }
}

Modern Minecraft. Older tutorials write a single pack_format number. As you learned in Chapter 9, current Minecraft uses min_format/max_format (here both 88, the value worked out in Chapter 28 for 1.21.9+). Leave the legacy pack_format field out of a new-only pack.

The load announcer

Give the pack a heartbeat: a load function that announces itself, wired through the minecraft:load function tag exactly as in Chapter 9, just in the new namespace.

emberforge/data/emberforge/function/load.mcfunction

# Emberforge content pack — runs on load/reload
say [Emberforge] forge fires lit. Pack loaded.

emberforge/data/minecraft/tags/function/load.json

{
  "values": [
    "emberforge:load"
  ]
}

Reload, and you should see the announcement. That’s your skeleton breathing.

Figure (to be captured). chat showing “[Emberforge] forge fires lit. Pack loaded.” after /reload

The Themed Items

The heart of the pack is three custom items. You already know how to build a custom item: it’s an item ID plus a bag of data components (Chapter 21), written in the bracket form item_id[component=value, ...]. We’ll define each item once, as a /give, inside a make_items function so you can summon all three for testing with one call. Then we’ll point each one at a custom model.

Item 1 — the Ember Blade

A golden sword that glows, named and colored to feel special, pre-sharpened, and aimed at a custom model. The components are all ones you met in Chapters 22–24:

  • custom_name — the hover name (a text component, Chapter 5/21).
  • lore — description lines (Chapter 22).
  • rarity"rare", which tints the name (Chapter 22).
  • enchantments — applied enchantments (Chapter 24).
  • item_model — points at the model definition in the resource pack (Chapter 22/28).

Item 2 — the Forgemaster’s Hammer

A pickaxe that mines quickly and hits a little harder. It uses functional components from Chapter 23:

  • tool — the mining-rule component (here we keep the default behavior and lean on attributes for the “fast” feel; full tool rules were covered in Chapter 23).
  • attribute_modifiers — a stat bonus (Chapter 23).
  • item_model — its custom look.

Item 3 — the Molten Bun

A quick snack, using the food components from Chapter 23:

  • food — nutrition and saturation.
  • consumable — how it’s eaten.
  • item_model — its look.

Here’s the function that gives all three to yourself:

emberforge/data/emberforge/function/make_items.mcfunction

# Emberforge — give the three themed items to the nearest player (for testing)

# 1. Ember Blade — glowing, sharp, "rare" sword
give @p minecraft:golden_sword[custom_name='{"text":"Ember Blade","color":"gold"}',lore=['{"text":"Forged in the first fire.","color":"dark_red"}'],rarity="rare",enchantments={"minecraft:sharpness":3},item_model="emberforge:ember_blade"]

# 2. Forgemaster's Hammer — fast pickaxe, harder hits
give @p minecraft:golden_pickaxe[custom_name='{"text":"Forgemaster\\u0027s Hammer","color":"gold"}',lore=['{"text":"Heavy as a falling anvil.","color":"gray"}'],rarity="rare",attribute_modifiers=[{type:"minecraft:attack_damage",amount:3,operation:"add_value",slot:"mainhand",id:"emberforge:hammer_damage"}],item_model="emberforge:forgemasters_hammer"]

# 3. Molten Bun — quick food
give @p minecraft:bread[custom_name='{"text":"Molten Bun","color":"gold"}',lore=['{"text":"Still warm.","color":"red"}'],food={nutrition:6,saturation:7.2},consumable={consume_seconds:0.8},item_model="emberforge:molten_bun"]

Run function emberforge:make_items and the three items appear in your inventory. They have their names, colors and behavior already, but they’ll show their base item picture (a golden sword, a golden pickaxe, bread) until we give them models. That’s the next step.

Under the Hood. Notice each give line is one long item argument. Components are separated by commas, each is component=value (an =, not a :, between the name and its value, Chapter 21), and the values are SNBT. The \\u0027 inside the name is just an escaped apostrophe so “Forgemaster’s” survives the single-quoted text component. If a line errors, the game points at the spot. Read it the way you practiced in Chapter 10.

Giving the items a custom look

The item_model component on each item is a pointer (Chapter 22): item_model="emberforge:ember_blade" tells the game to read the item model definition at assets/emberforge/items/ember_blade.json. You built exactly these definition files in Chapter 29. Here we keep them at their simplest (one model, one texture) because the assembly, not the modeling, is the lesson. Each definition is the minimal type: minecraft:model form from Chapter 29:

emberforge_resources/assets/emberforge/items/ember_blade.json

{
  "model": {
    "type": "minecraft:model",
    "model": "emberforge:item/ember_blade"
  }
}

emberforge_resources/assets/emberforge/items/forgemasters_hammer.json

{
  "model": {
    "type": "minecraft:model",
    "model": "emberforge:item/forgemasters_hammer"
  }
}

emberforge_resources/assets/emberforge/items/molten_bun.json

{
  "model": {
    "type": "minecraft:model",
    "model": "emberforge:item/molten_bun"
  }
}

Each model field names a shape file in assets/emberforge/models/item/ that wears a texture in assets/emberforge/textures/item/: the three-folder chain you built in Chapter 29. We won’t reprint the shape and texture files here (that’s Chapter 29’s job); make a small PNG and a one-line model file for each, exactly as you did for the flame sword, and your items will wear their own art.

Try It! Want the Ember Blade to glow only when it carries an enchantment, like the flame sword in Chapter 29? Swap its items/ember_blade.json for a minecraft:condition definition on the enchantment glint. The recipe in this chapter already adds Sharpness, so it’ll glow by default, but the conditional version is a nice touch if you make an unenchanted variant.

Recipes to Craft Them

Right now the only way to get an Emberforge item is the make_items cheat. Let’s make them craftable. You wrote recipes in Chapter 15; the key idea here is that a recipe’s result can carry its own components, so the crafted item comes out already customized: the same component bag as the give above, just attached to the recipe’s result.

The result field is id (not the legacy item), and components is optional extra data on the result. Here are the three recipes.

The Ember Blade is a shaped recipe, two ember-ish ingredients over a stick, like a sword:

emberforge/data/emberforge/recipe/ember_blade.json

{
  "type": "minecraft:crafting_shaped",
  "category": "equipment",
  "group": "emberforge",
  "pattern": [
    "B",
    "B",
    "S"
  ],
  "key": {
    "B": "minecraft:blaze_powder",
    "S": "minecraft:stick"
  },
  "result": {
    "id": "minecraft:golden_sword",
    "components": {
      "minecraft:custom_name": "{\"text\":\"Ember Blade\",\"color\":\"gold\"}",
      "minecraft:lore": [
        "{\"text\":\"Forged in the first fire.\",\"color\":\"dark_red\"}"
      ],
      "minecraft:rarity": "rare",
      "minecraft:enchantments": {
        "minecraft:sharpness": 3
      },
      "minecraft:item_model": "emberforge:ember_blade"
    }
  }
}

The Forgemaster’s Hammer, a pickaxe shape in blaze powder and gold:

emberforge/data/emberforge/recipe/forgemasters_hammer.json

{
  "type": "minecraft:crafting_shaped",
  "category": "equipment",
  "group": "emberforge",
  "pattern": [
    "BBB",
    " S ",
    " S "
  ],
  "key": {
    "B": "minecraft:blaze_powder",
    "S": "minecraft:stick"
  },
  "result": {
    "id": "minecraft:golden_pickaxe",
    "components": {
      "minecraft:custom_name": "{\"text\":\"Forgemaster's Hammer\",\"color\":\"gold\"}",
      "minecraft:lore": [
        "{\"text\":\"Heavy as a falling anvil.\",\"color\":\"gray\"}"
      ],
      "minecraft:rarity": "rare",
      "minecraft:attribute_modifiers": [
        {
          "type": "minecraft:attack_damage",
          "amount": 3,
          "operation": "add_value",
          "slot": "mainhand",
          "id": "emberforge:hammer_damage"
        }
      ],
      "minecraft:item_model": "emberforge:forgemasters_hammer"
    }
  }
}

The Molten Bun, a shapeless recipe (toss the ingredients in any order):

emberforge/data/emberforge/recipe/molten_bun.json

{
  "type": "minecraft:crafting_shapeless",
  "category": "misc",
  "group": "emberforge",
  "ingredients": [
    "minecraft:bread",
    "minecraft:blaze_powder"
  ],
  "result": {
    "id": "minecraft:bread",
    "components": {
      "minecraft:custom_name": "{\"text\":\"Molten Bun\",\"color\":\"gold\"}",
      "minecraft:lore": [
        "{\"text\":\"Still warm.\",\"color\":\"red\"}"
      ],
      "minecraft:food": {
        "nutrition": 6,
        "saturation": 7.2
      },
      "minecraft:consumable": {
        "consume_seconds": 0.8
      },
      "minecraft:item_model": "emberforge:molten_bun"
    }
  }
}

Reload, open a crafting table, and the three recipes are yours. Because the components live on the result, the crafted items come out fully themed: names, colors, behavior and model all attached.

What Went Wrong? If a crafted item appears with the right name but the wrong picture, the recipe’s item_model is fine but the resource pack isn’t loaded. Remember a resource pack is enabled in its own menu (Chapter 28), not with /datapack. If the item comes out as plain gold with no name at all, check that you wrote id in the result, not item.

The Cache Chest Loot Table

In the story, these items are found in a forge cache, a chest hidden in a structure. We’ll build the structure itself in Part XI (it’s a world-generation topic). What we can build now, and what the structure would point at, is the loot table that fills the chest. You wrote loot tables in Chapters 16 and 17; this one drops the themed items using set_components so each rolled item carries its full component bag.

A chest table declares a chest loot context: the loot situation of a container being opened.

Chest loot context. Minecraft lists “opening of a container with loot table” (barrel, chest, trapped chest, and so on) as one of the loot-context types, with the chest’s center as the Origin and the opener as the this entity. A loot table sets its context with the type field (Chapter 17). For a chest we use type: minecraft:chest.

Here’s the cache table. Two pools: one guaranteed roll that always gives the Ember Blade (the prize), and one pool that rolls 1–2 times for a hammer or some buns.

emberforge/data/emberforge/loot_table/cache.json

{
  "type": "minecraft:chest",
  "pools": [
    {
      "rolls": 1,
      "entries": [
        {
          "type": "minecraft:item",
          "name": "minecraft:golden_sword",
          "functions": [
            {
              "function": "minecraft:set_components",
              "components": {
                "minecraft:rarity": "rare",
                "minecraft:enchantments": {
                  "minecraft:sharpness": 3
                },
                "minecraft:item_model": "emberforge:ember_blade"
              }
            },
            {
              "function": "minecraft:set_name",
              "name": "{\"text\":\"Ember Blade\",\"color\":\"gold\"}",
              "target": "custom_name"
            },
            {
              "function": "minecraft:set_lore",
              "lore": [
                "{\"text\":\"Forged in the first fire.\",\"color\":\"dark_red\"}"
              ],
              "mode": "replace_all"
            }
          ]
        }
      ]
    },
    {
      "rolls": {
        "min": 1,
        "max": 2
      },
      "entries": [
        {
          "type": "minecraft:item",
          "name": "minecraft:golden_pickaxe",
          "weight": 2,
          "functions": [
            {
              "function": "minecraft:set_components",
              "components": {
                "minecraft:rarity": "rare",
                "minecraft:item_model": "emberforge:forgemasters_hammer"
              }
            },
            {
              "function": "minecraft:set_name",
              "name": "{\"text\":\"Forgemaster's Hammer\",\"color\":\"gold\"}",
              "target": "custom_name"
            }
          ]
        },
        {
          "type": "minecraft:item",
          "name": "minecraft:bread",
          "weight": 5,
          "functions": [
            {
              "function": "minecraft:set_count",
              "count": {
                "min": 1,
                "max": 3
              }
            },
            {
              "function": "minecraft:set_components",
              "components": {
                "minecraft:food": {
                  "nutrition": 6,
                  "saturation": 7.2
                },
                "minecraft:item_model": "emberforge:molten_bun"
              }
            },
            {
              "function": "minecraft:set_name",
              "name": "{\"text\":\"Molten Bun\",\"color\":\"gold\"}",
              "target": "custom_name"
            }
          ]
        }
      ]
    }
  ]
}

Testing the table without a structure

You don’t need the structure to test the loot: the /loot command runs any table on demand. There are two forms you’ll use. To drop the cache’s contents straight into your own inventory:

/loot give @s loot emberforge:cache

To fill a real chest you’ve placed, aim at it and insert:

/loot insert ~ ~ ~-1 loot emberforge:cache

(/loot give <players> loot <table> gives the items to a player; /loot insert <pos> loot <table> puts them into the container at that position, both from the /loot command’s source list.) Run it a few times: you should always get an Ember Blade, plus one or two hammers or stacks of buns.

Figure (to be captured). a chest opened to show an Ember Blade plus Molten Buns, all custom-named and gold

A Custom Structure (Preview Only)

So where does this cache live? In a structure, a built piece that generates in the world. A structure is “a large decoration… configured using JSON files within a data pack in the path data/<namespace>/worldgen/structure,” which only generates once it’s “part of at least one structure set.” A structure set then decides where in the world the piece appears.

Building a real structure (the .nbt building file, the worldgen/structure definition, the structure_set placement, jigsaw blocks, template pools) is the subject of Part XI (Chapters 40–41). It’s genuinely more involved than anything in this part, which is why it lives there. For this capstone, the takeaway is the connection: a structure’s chest would simply point its loot at the table you just wrote (emberforge:cache). You’ve already built the part that matters for the items (the loot) and you’ve tested it. When you reach Chapter 40, you’ll save a forge building, and in Chapter 41 you’ll place it in the world with its chest aimed at this exact table.

Note. We’re deliberately not writing worldgen/structure or structure_set JSON here. Those formats belong to Part XI. Treating the cache as “a loot table now, a structure later” keeps this chapter focused on assembly and lets the structure chapters teach world generation properly.

The Advancement Tree

A content pack feels finished when it guides the player. You’ll build a small advancement tree (Chapter 19) with three steps that detect the player’s progress and, at the end, fire a function.

All three use the inventory_changed trigger, which fires whenever the player’s inventory changes, with an items condition that checks what they’re now holding. The item condition gives us the fields we need: an items list (which item types match) and a components object that “matches exact item component values.” We detect an Emberforge item by checking its item_model component.

Step 1, the root, “Apprentice.” Fires when the player obtains any item carrying an Emberforge model. (We check the Ember Blade’s model as the representative; you could widen this to a list.)

emberforge/data/emberforge/advancement/root.json

{
  "display": {
    "icon": {
      "id": "minecraft:blaze_powder"
    },
    "title": {
      "text": "Apprentice",
      "color": "gold"
    },
    "description": {
      "text": "Obtain your first Emberforge item.",
      "color": "gray"
    },
    "frame": "task",
    "background": "minecraft:textures/block/netherrack.png",
    "show_toast": true,
    "announce_to_chat": true
  },
  "criteria": {
    "got_item": {
      "trigger": "minecraft:inventory_changed",
      "conditions": {
        "items": [
          {
            "components": {
              "minecraft:item_model": "emberforge:ember_blade"
            }
          }
        ]
      }
    }
  }
}

Step 2, the child, “Swordsmith.” A child advancement (it names the root as its parent), granted when the player holds the Ember Blade specifically.

emberforge/data/emberforge/advancement/ember_blade.json

{
  "parent": "emberforge:root",
  "display": {
    "icon": {
      "id": "minecraft:golden_sword"
    },
    "title": {
      "text": "Swordsmith",
      "color": "gold"
    },
    "description": {
      "text": "Hold the Ember Blade.",
      "color": "gray"
    },
    "frame": "goal",
    "show_toast": true,
    "announce_to_chat": true
  },
  "criteria": {
    "has_blade": {
      "trigger": "minecraft:inventory_changed",
      "conditions": {
        "items": [
          {
            "items": [
              "minecraft:golden_sword"
            ],
            "components": {
              "minecraft:item_model": "emberforge:ember_blade"
            }
          }
        ]
      }
    }
  }
}

Step 3, the hidden detector, “Cache Opener.” This one has no toast meant to be seen as a normal goal. Instead it’s the hidden-advancement-as-detector pattern from Chapter 19: it watches for the player obtaining a special cache marker item, then runs a reward function and immediately revokes itself so it can fire again. The marker is just bread carrying a custom_data flag (Chapter 24) that the cache chest’s structure would hand out; for testing you can give it yourself.

emberforge/data/emberforge/advancement/open_cache.json

{
  "criteria": {
    "got_marker": {
      "trigger": "minecraft:inventory_changed",
      "conditions": {
        "items": [
          {
            "components": {
              "minecraft:custom_data": {
                "emberforge_cache": true
              }
            }
          }
        ]
      }
    }
  },
  "rewards": {
    "function": "emberforge:cache_reward"
  }
}

Notice this one has no display block at all. Advancements may “lack a display so that they can utilize triggers and rewards instead of excessive commands”: exactly the silent-event- listener role from Chapter 19. Its reward fires the function below.

emberforge/data/emberforge/function/cache_reward.mcfunction

# Emberforge — fired when the player opens the cache (hidden advancement reward)
playsound emberforge:cache_open master @s
tellraw @s {"text":"The forge remembers you.","color":"gold"}

# re-arm: revoke this advancement so it can fire again next time
advancement revoke @s only emberforge:open_cache

That last line is the re-arming trick from Chapter 19: revoking the hidden advancement on the player means the next time they pick up a cache marker, it triggers all over again.

Figure (to be captured). the “Apprentice” and “Swordsmith” advancements in a custom tab with a netherrack background

Custom Sound for Polish

The reward function plays emberforge:cache_open, a sound that doesn’t exist yet. Register it the way you learned in Chapter 30: a sounds.json in the resource pack that maps the event name to one or more .ogg files.

emberforge_resources/assets/emberforge/sounds.json

{
  "cache_open": {
    "subtitle": "Emberforge cache opens",
    "sounds": [
      "emberforge:cache_open"
    ]
  }
}

Then place your OGG file at assets/emberforge/sounds/cache_open.ogg. Remember that /playsound takes an event name, not a file name. The event here is emberforge:cache_open (the emberforge namespace plus the cache_open key from sounds.json), and the sounds list points at the file. That’s why the reward function says playsound emberforge:cache_open master @s. The subtitle line shows in closed captions if the player has them on.

Modern Minecraft. The event’s namespace comes from where the sounds.json lives, not from a field inside it. The file is under assets/emberforge/, so its events are emberforge:<key>. This is why the same cache_open key inside an assets/minecraft/sounds.json would instead be a vanilla event. Keep your sounds under your own namespace and they won’t collide with anyone else’s pack.

The Full Project Tree

Here is everything you built, both packs side by side. (Model and texture files from Chapter 29, and the .ogg, are marked but not reprinted; they’re the same art-pipeline files from earlier chapters.)

emberforge/                                              <- DATA pack
  pack.mcmeta
  data/
    minecraft/
      tags/function/load.json
    emberforge/
      function/
        load.mcfunction
        make_items.mcfunction
        cache_reward.mcfunction
      recipe/
        ember_blade.json
        forgemasters_hammer.json
        molten_bun.json
      loot_table/
        cache.json
      advancement/
        root.json
        ember_blade.json
        open_cache.json

emberforge_resources/                                    <- RESOURCE pack
  pack.mcmeta
  assets/
    emberforge/
      sounds.json
      items/
        ember_blade.json
        forgemasters_hammer.json
        molten_bun.json
      models/item/    (ember_blade.json, forgemasters_hammer.json, molten_bun.json — Chapter 29)
      textures/item/  (ember_blade.png, forgemasters_hammer.png, molten_bun.png — Chapter 29)
      sounds/         (cache_open.ogg — Chapter 30)

Testing Everything Together

A content pack has many moving parts, so test it as a routine, top to bottom, every time you change something. Here’s the checklist for Emberforge:

  1. Install both packs. Put emberforge/ in the world’s datapacks/ folder; bundle emberforge_resources/ as a resources.zip (Chapter 28) and enable it in Options → Resource Packs. Two packs, two different menus.
  2. Reload. Run /reload. You should see the [Emberforge] forge fires lit announcement.
  3. Items. Run function emberforge:make_items. All three items appear, named and colored, wearing their custom models.
  4. Recipes. Open a crafting table and craft each of the three. The crafted items should match the given ones exactly.
  5. Loot. Place a chest, aim at it, run /loot insert ~ ~ ~-1 loot emberforge:cache a few times. You should always get an Ember Blade plus a hammer or buns. Try /loot give @s loot emberforge:cache too.
  6. Advancements. Obtain an Emberforge item → “Apprentice” pops. Hold the Ember Blade → “Swordsmith” pops. Give yourself the cache marker (give @s minecraft:bread[custom_data={emberforge_cache:true}]) → the hidden detector fires the reward.
  7. Sound. When the hidden detector fires, you should hear emberforge:cache_open and see “The forge remembers you.” If you hear nothing, the sound event or the .ogg path is wrong. Check sounds.json and that the resource pack is actually enabled.

When every line passes, your content pack is complete and shippable. (Chapters 46–47 cover versioning and publishing it for other players.)

Practice

  1. A fourth item. Add an Ember Charm (use any item ID, e.g. minecraft:gold_nugget) with a custom_name, rarity, and item_model. Give it a shapeless recipe, add it as a low-weight entry in cache.json, and add an item_model definition for it. Run your test checklist again. The new item should flow through every system.
  2. A second advancement branch. Add a child advancement under the root, “Baker,” that fires on holding a Molten Bun (inventory_changed with the bun’s item_model in components). Give it a goal frame. Now your tree branches: Apprentice → Swordsmith and Apprentice → Baker.
  3. Widen the root. Change the root’s items condition into a list of all three Emberforge models so any of them triggers “Apprentice,” not just the blade. (Hint: the items field is already a list, so add more entries, each with a different item_model.)

What Can Go Wrong

  • Namespace mismatch between the two packs. If your data pack uses emberforge but you put the models under assets/ember_forge/, every item_model pointer misses and items show the error model. One project, one namespace: check both packs spell it the same way.
  • Forgetting the resource pack. Data packs and resource packs enable in different menus (Chapter 28). If items have the right names and behavior but wrong pictures, or the custom sound is silent, the resource pack almost certainly isn’t loaded.
  • Wrong loot type. A chest table needs "type": "minecraft:chest". If you copy a mob table’s context by accident, conditions that expect a chest context won’t behave. (For this simple table it’ll still drop items, but get into the habit of matching the context to where the table is used.)

What You Know Now

You can plan a content pack on paper, then build it: a paired data pack and resource pack under one project namespace, with custom items (components + models), recipes that craft them, a loot table that drops them, an advancement tree that guides the player, and a custom sound for polish, and you can test the whole thing end to end. This is the capstone of Parts I–VIII: it ties together components (21–24), recipes (15), loot tables (16–17), advancements (19), resource-pack models (29) and sounds (30) into one shippable project. The one piece you’ve previewed but not yet built is the structure that houses the cache. That’s where Part XI begins.

Chapter 35 — Data-Driven Enchantments

What You’ll Build

Welcome to Part X. From here on the chapters are à la carte: each one opens a self-contained system you can pick up when a project needs it, and they all assume you’ve worked through Parts I through VIII. This first one answers a question every Minecraft player eventually asks: can I make my own enchantment? The answer, in modern Minecraft, is yes, and you make one the same way you’ve made everything else in this book, by writing a JSON file.

By the end of this chapter you will have:

  • A brand-new enchantment called Frostbite (a file at data/mypack/enchantment/frostbite.json) that slows down any mob your enchanted weapon hits.
  • A set of small enchantment tag files that tell the game your enchantment exists in the world: which items can wear it, whether the enchanting table can offer it, and what it conflicts with.
  • A modified vanilla enchantment: you’ll override Minecraft’s own sharpness.json to change how it behaves, without touching a single line of the game’s code.

And you’ll learn one rule that trips up almost everyone the first time: enchantments do not reload with /reload. They need a world reboot, and we’ll explain exactly why.

mypack/
  data/
    mypack/
      enchantment/
        frostbite.json                       (the new enchantment itself)
      tags/
        enchantment/
          in_enchanting_table.json           (lets the table offer Frostbite)
          tooltip_order.json                 (where it sits in the tooltip — optional)
          exclusive_set/
            frostbite_group.json             (what Frostbite conflicts with)
    minecraft/
      enchantment/
        sharpness.json                       (our override of the vanilla enchantment)

Concepts

An enchantment is a file

Back in Chapter 7 you learned the big idea of this whole book: vanilla Minecraft is itself a data pack, and almost everything in the game is defined by data files you’re allowed to replace. An enchantment definition is one of those files. Put plainly: enchantments are stored as JSON files within a data pack in the path data/<namespace>/enchantment.

So Sharpness, Mending, Protection (every enchantment you’ve ever used) is a JSON file sitting inside Minecraft’s own minecraft namespace. To make a new one, you drop a file into your namespace’s enchantment/ folder. To change an existing one, you put a file with the same name in the minecraft namespace and yours wins. That’s the entire trick; the rest of this chapter is learning what goes inside the file.

Modern Minecraft. If you’ve read older tutorials, you may have seen people insist that custom enchantments are “impossible without mods,” and that the closest you can get is faking one with scoreboards and command blocks. That was true for years. It stopped being true when enchantments became data-driven: now an enchantment is a file like a recipe or a loot table, and a plain data pack can add a real one that shows up in the enchanting table and on the item tooltip.

The reboot caveat (this one matters)

Here is the rule that will save you an hour of confusion. In Chapter 8 you learned /reload, the command that re-reads your data pack so edits apply without leaving the world. It works on functions, recipes, loot tables, advancements, tags: most of what you’ve built so far.

It does not work on enchantments.

The reason goes back to a distinction from Chapter 7: a dynamic registry is a registry (a master list the game keeps) that data packs are allowed to add content to, but which the game only assembles when a world loads. Enchantments live in a dynamic registry. So do biomes and dimensions, which you’ll meet later. Because the list is built at world-load time, /reload, which only re-reads the hot-reloadable files, never touches it.

To see a change to any enchantment file, you must leave the world and re-enter it (in single player: quit to the title screen or to “Save and Quit,” then open the world again). On a server, that means a full server restart. There is no command shortcut. Whenever something in this chapter “isn’t working,” the very first thing to check is whether you rebooted the world after your last edit.

Under the Hood (skippable). “Hot-reloadable” data (recipes, loot tables, functions) is re-read every /reload because the game can swap it out mid-game safely. Dynamic-registry data (enchantments, biomes, dimensions, damage types) helps define the shape of the world itself, so the game freezes it at load time for consistency and only rebuilds it on a fresh load. You don’t need to remember the mechanism — just the rule: dynamic registry → reboot, not /reload.


Walkthrough A — The anatomy of an enchantment definition

Let’s read a complete enchantment file top to bottom, then build our own. Every field below is named exactly as the Enchantment definition page on the wiki names it. Here is Frostbite, our weapon enchantment that slows whatever it hits. Don’t worry about the effects block yet; we’ll take that apart in Walkthrough B. Read the fields above it first.

data/mypack/enchantment/frostbite.json

{
  "description": {
    "translate": "enchantment.mypack.frostbite",
    "fallback": "Frostbite"
  },
  "supported_items": "#minecraft:enchantable/sharp_weapon",
  "primary_items": "#minecraft:enchantable/sharp_weapon",
  "weight": 5,
  "max_level": 3,
  "min_cost": {
    "base": 10,
    "per_level_above_first": 8
  },
  "max_cost": {
    "base": 40,
    "per_level_above_first": 8
  },
  "anvil_cost": 4,
  "slots": [
    "mainhand"
  ],
  "exclusive_set": "#mypack:exclusive_set/frostbite_group",
  "effects": {
    "minecraft:post_attack": [
      {
        "enchanted": "attacker",
        "affected": "victim",
        "effect": {
          "type": "minecraft:apply_mob_effect",
          "to_apply": "minecraft:slowness",
          "min_duration": 1.0,
          "max_duration": 3.0,
          "min_amplifier": 0.0,
          "max_amplifier": 1.0
        }
      }
    ]
  }
}

That’s the whole file. Now the fields, in order:

  • description is a text component (the same kind of styled-text object you built in Chapter 5) that’s used to display the enchantment’s name on items. Here we use a translate key (so the name can be localized) with a fallback string so it reads “Frostbite” even before you add a language file. A plain "description": "Frostbite" works too if you don’t care about translation.

  • supported_items is the set of items this enchantment can be applied to using an anvil or the /enchant command. It’s written as a single item, a list of items, or (most usefully) an item tag with a leading #, exactly like the tags you learned in Chapter 14. We used #minecraft:enchantable/sharp_weapon, the built-in tag standing for “all swords and axes” (it’s the same tag vanilla Sharpness uses), so Frostbite goes on any of them without us listing items one by one.

  • primary_items (optional) is the set of items the enchantment appears on in an enchanting table, and it must be a subset of supported_items. If you leave it out, it defaults to being the same as supported_items. This is the file-level version of the “primary vs secondary items” idea from vanilla: primary items can get the enchantment at the table; items that are only in supported_items can get it from a book on an anvil but never from the table.

  • weight is a value from 1 to 1024 that controls how likely this enchantment is to be offered when enchanting. The probability is weight / total_weight, where total_weight is the sum of the weights of every enchantment available for that item. Rare vanilla enchantments use low weights; common ones use high weights. Our 5 makes Frostbite uncommon.

  • max_level is the highest level the enchantment can reach, from 1 to 255. Ours caps at 3 (you’ll see “Frostbite III” as the strongest version).

  • min_cost and max_cost together set the cost range in levels the enchanting table uses when deciding whether to offer this enchantment. Each is a small object with two fields: base, the cost for a level-I enchantment, and per_level_above_first, how much to add for each level beyond the first. So our min_cost is 10 at level I, 18 at level II, 26 at level III. (The table modifies this range further with some bookshelf math, but base and per_level_above_first are the numbers you control.)

  • anvil_cost is the base experience-level cost of applying this enchantment with an anvil. It’s halved when you apply it from a book, and multiplied by the enchantment’s level.

  • slots is the list of equipment slots the enchantment actually works in. Each entry is one of any, hand, mainhand, offhand, armor, feet, legs, chest, head, body, or saddle. A weapon enchantment like ours only does anything in mainhand. An armor enchantment would use a slot like chest or the catch-all armor; a boots enchantment, feet.

  • exclusive_set lists enchantments that are incompatible with this one: the file-level version of vanilla’s conflicts (you can’t put Sharpness and Smite on the same sword). It’s an enchantment, a list of enchantments, or an enchantment tag, and defaults to an empty list (no conflicts) if you omit it. We pointed ours at a tag, #mypack:exclusive_set/frostbite_group, which we’ll create in Walkthrough C.

  • effects is the heart of the file: the block that says what the enchantment does. That’s the whole next section.

Try It! Change max_level to 1 and weight to 30, and reboot the world. Frostbite is now a common, single-level enchantment, much more likely to show up at a table. Tweaking weight, max_level, and the cost range is how vanilla makes some enchantments feel rare and others routine, and you have exactly the same dials.


Walkthrough B — The effects block: what an enchantment does

Every field so far described where the enchantment lives and how you obtain it. The effects block describes what happens when it’s equipped. This is where data-driven enchantments do the most, and it’s worth going slowly.

The effects field holds effect components, and it controls what the enchantment does.

An effect component is one entry inside the effects block. Its key is the kind of moment the effect hooks into: for example minecraft:post_attack (right after you hit something), minecraft:damage_immunity (deciding whether you take damage), or minecraft:location_changed (when the wearer moves). The value attached to that key describes what to do at that moment. Our Frostbite used minecraft:post_attack, so it fires every time the enchanted weapon lands a hit.

Inside an effect component, the action you take is itself one of three families:

  1. A value effect changes a number, like adding to the damage dealt, or to mining speed. These use the small type-based shapes minecraft:add, minecraft:set, minecraft:multiply, minecraft:remove_binomial, and minecraft:all_of. (Sharpness, under the hood, is a value effect that adds to damage.)

  2. An entity effect does something to a creature: applies a status effect, deals damage, sets it on fire, spawns particles. This is the family Frostbite uses.

  3. A location-based effect happens at a place. Frost Walker freezing water beneath you is the classic example.

Frostbite’s effect is an entity effect of type minecraft:apply_mob_effect, which applies a status effect to the affected mob. Look back at our file and read the inner object:

"effect": {
  "type": "minecraft:apply_mob_effect",
  "to_apply": "minecraft:slowness",
  "min_duration": 1.0,
  "max_duration": 3.0,
  "min_amplifier": 0.0,
  "max_amplifier": 1.0
}

Each field is named exactly as the apply_mob_effect entry names it:

  • to_apply is the status effect (or a tag of effects) to apply. We chose minecraft:slowness.
  • min_duration / max_duration are the shortest and longest possible duration in seconds. The game picks a value in that range, so Frostbite’s slow lasts somewhere from 1 to 3 seconds.
  • min_amplifier / max_amplifier are the weakest and strongest amplifier (Slowness I has amplifier 0, Slowness II has amplifier 1). Ours ranges from 0 to 1, so it’s sometimes Slowness I, sometimes II.

Two more fields wrap the effect, and they belong to the post_attack component specifically:

  • enchanted — which entity must be carrying the enchantment for this to fire. One of attacker, victim, or damaging_entity. We used attacker: you, the one swinging the enchanted sword.
  • affected — which entity the effect lands on. Also one of attacker, victim, or damaging_entity. We used victim: the mob you hit. So: the attacker holds Frostbite; the victim gets the slow. Swap those two words and you’d slow yourself, a useful sanity check.

Under the Hood (skippable). Most numbers in the effects block can be a level-based value instead of a plain number, so a higher enchantment level does more. The simplest form is minecraft:linear, defined as base + per_level_above_first * (level - 1). If we wanted Frostbite’s slow to last longer at higher levels, we’d replace "max_duration": 3.0 with:

"max_duration": {
  "type": "minecraft:linear",
  "base": 3.0,
  "per_level_above_first": 1.5
}

Now Frostbite I slows for up to 3 seconds, Frostbite II up to 4.5, Frostbite III up to 6. There are also minecraft:levels_squared, minecraft:fraction, minecraft:clamped, and a minecraft:lookup form that lists a value per level, but linear covers most cases and a plain constant covers the rest.

The full menu of effect components and effects is large: there are value effects, many entity effects (damage_entity, ignite, explode, spawn_particles, play_sound, summon_entity, run_function, and more), and location-based effects. You don’t need them memorized. You need to know the shape: an effect component keyed by a moment, holding an effect of a known type, optionally gated by requirements (an inline predicate, the Chapter 18 kind). When you want a behavior, open the Enchantment definition page, find the effect whose name matches what you want, and copy its fields.

Try It! Make a “Searing Edge” enchantment that sets the victim on fire instead of slowing it. Keep the whole post_attack wrapper (enchanted: attacker, affected: victim) and swap the inner effect for the minecraft:ignite shape: {"type": "minecraft:ignite", "duration": 4.0}. Reboot the world and hit a pig.


Walkthrough C — Enchantment tags: telling the world your enchantment exists

You’ve written the enchantment. But by itself, a definition file is just a possible enchantment: the game knows the recipe but won’t yet hand it out at a table, and doesn’t know what it conflicts with. That wiring is done with enchantment tags.

An enchantment tag is exactly the kind of tag you learned in Chapter 14, a values list grouping members of one registry, except the registry is enchantment. So the files live at data/<namespace>/tags/enchantment/, and their purpose is simple: an enchantment tag is a group of enchantments, used in loot tables, to configure exclusive enchantments, and in other gameplay features.

The tags Minecraft itself reads (the ones with real effects) all live in the minecraft namespace, and you add to them the same way you added to #minecraft:logs in Chapter 14: by creating a file with the same name in your pack and listing your member, leaving replace at its default of false so you extend the vanilla tag instead of wiping it.

1. Make Frostbite appear at the enchanting table. The in_enchanting_table tag holds the enchantments that can be obtained from an enchanting table. Without being in it, your enchantment can only be applied with /enchant or a command, never rolled at a table. Add yours:

data/mypack/tags/enchantment/in_enchanting_table.json

{
  "values": [
    "mypack:frostbite"
  ]
}

2. Define the exclusive set. Back in Frostbite’s definition we set "exclusive_set": "#mypack:exclusive_set/frostbite_group". Vanilla keeps its conflict groups as tags under exclusive_set/ (for example exclusive_set/armor, exclusive_set/bow), each one a group whose members are mutually exclusive with each other. Let’s make our own, putting Frostbite and the vanilla Bane of Arthropods in the same group so a sword can’t have both:

data/mypack/tags/enchantment/exclusive_set/frostbite_group.json

{
  "values": [
    "mypack:frostbite",
    "minecraft:bane_of_arthropods"
  ]
}

Note the path mirrors the tag id: a file at tags/enchantment/exclusive_set/frostbite_group.json in the mypack namespace is the tag #mypack:exclusive_set/frostbite_group, which is exactly the id we referenced in the definition. (Subfolders just become part of the name, the same rule you saw with block tags in Chapter 14.)

3. (Optional) Place it in the tooltip order. The tooltip_order tag controls the order of enchantments displayed in an item’s tooltip. If you don’t add to it, your enchantment still shows up; it just lands at the end. To slot it deliberately, extend the vanilla tag:

data/mypack/tags/enchantment/tooltip_order.json

{
  "values": [
    "mypack:frostbite"
  ]
}

There are many other enchantment tags, and they’re worth skimming on the Enchantment tag (Java Edition) page when you build something specific. A few you’ll reach for:

  • tradeable — enchantments villagers can sell.
  • treasure / non_treasure — whether it counts as a “treasure” enchantment (table-banned, book-only, like vanilla Mending).
  • curse — red-text, can’t-be-removed enchantments, like Curse of Binding.
  • on_random_loot / on_mob_spawn_equipment — whether it can appear on generated loot or on mobs that spawn with gear.

Modern Minecraft. This tag system is why a plain data pack can now do what once needed a mod. The behaviors that used to be hard-coded into Minecraft’s engine (“which enchantments the table offers,” “which ones conflict,” “which villagers sell”) are all just tag memberships you’re allowed to edit. Your enchantment becomes a first-class citizen by joining the same lists the vanilla ones are in.


Walkthrough D — Modifying a vanilla enchantment

Creating a new enchantment and changing an existing one are the same move with a different file name. Because every vanilla enchantment is a file in the minecraft namespace, you override one by writing a file with the matching name in your pack’s minecraft namespace. Yours loads on top.

Say you think Sharpness is too weak. Sharpness lives at data/minecraft/enchantment/sharpness.json inside the game; to override it, you write your own copy at that same path in your pack and bump its numbers. The catch, and this matters, is that an override replaces the whole file, not just the fields you mention. So you must supply every field Sharpness needs, not only the ones you’re changing. Here is a complete override that gives Sharpness a much steeper damage curve:

data/minecraft/enchantment/sharpness.json

{
  "description": {
    "translate": "enchantment.minecraft.sharpness"
  },
  "supported_items": "#minecraft:enchantable/sharp_weapon",
  "primary_items": "#minecraft:enchantable/sharp_weapon",
  "weight": 10,
  "max_level": 5,
  "min_cost": {
    "base": 1,
    "per_level_above_first": 11
  },
  "max_cost": {
    "base": 21,
    "per_level_above_first": 11
  },
  "anvil_cost": 1,
  "slots": [
    "mainhand"
  ],
  "exclusive_set": "#minecraft:exclusive_set/damage",
  "effects": {
    "minecraft:damage": [
      {
        "effect": {
          "type": "minecraft:add",
          "value": {
            "type": "minecraft:linear",
            "base": 2.0,
            "per_level_above_first": 2.0
          }
        }
      }
    ]
  }
}

The only thing we actually changed from vanilla is the damage formula in the effects block: a value effect of type minecraft:add whose value is a minecraft:linear level-based value with base 2.0 and per_level_above_first 2.0, so Sharpness I adds 2 damage, Sharpness V adds 10 (vanilla adds far less). Everything else is reproduced exactly so the override doesn’t accidentally break the enchantment’s name, cost, or conflicts.

This is the cleanest illustration of the chapter’s whole idea: you didn’t mod the game, you handed it a replacement file. Reboot the world, enchant a sword with Sharpness, and check the damage.

What Went Wrong? You overrode sharpness.json but it vanished from the enchanting table entirely. Almost always this means a typo or a missing required field made the file invalid, so the game couldn’t load it. Check the game log (Chapter 10) right after the world loads; a malformed enchantment reports an error there. And remember the override is all-or-nothing: leaving out slots or supported_items doesn’t “keep the vanilla value,” it produces a broken file.


Practice — A custom enchantment with unique behavior

Time to put it together in mypack. Build Frostbite end-to-end and prove it works in-game.

  1. Write the definition. Create data/mypack/enchantment/frostbite.json from Walkthrough A, effects block and all. Save it.

  2. Register the tags. Create the three tag files from Walkthrough C: in_enchanting_table.json (so the table offers it), exclusive_set/frostbite_group.json (so it conflicts with Bane of Arthropods), and optionally tooltip_order.json.

  3. Reboot the world. Save and quit to the title screen, then re-open the world. This is the step everyone forgets, and /reload will not pick up your enchantment.

  4. Apply it and test. The fastest way to put your enchantment on a held sword is the /enchant command, which “adds an enchantment to a player’s selected item.” Hold a sword and run, in chat:

    /enchant @s mypack:frostbite 2
    

    That gives you Frostbite II. Now hit a mob (a pig, a zombie) and watch it slow down. (If you’d rather build it into your pack, the same command works inside a .mcfunction file with the / dropped, exactly as in earlier chapters.)

  5. Find it at a table. Because you added Frostbite to in_enchanting_table, enchant a fresh sword at an enchanting table a few times. With enough tries (its weight is only 5) Frostbite will be one of the offered options: proof your enchantment is a full citizen of the game now.

Extensions (each is a small edit, then a reboot):

  • Make it scale. Use the minecraft:linear level-based value from Walkthrough B so higher levels slow for longer.
  • Change the payload. Swap apply_mob_effect for minecraft:ignite (“Searing Edge”) or, once you’ve read Chapter 36 on damage types, minecraft:damage_entity for an enchantment that deals bonus damage of a type you define.
  • Armor instead of weapon. Make a defensive enchantment: change supported_items to an armor tag, set slots to ["chest"] or ["armor"], and use a post_attack effect where the enchanted entity is the victim and the affected is the attacker, so being hit punishes the attacker.

What Can Go Wrong

  • You edited the file and /reloaded, but nothing changed. This is the number-one Frostbite bug, and it isn’t a bug at all. Enchantments are a dynamic registry; /reload never touches them. Quit to the title screen and re-open the world (restart the server) after every enchantment or enchantment-tag edit. If a change seems ignored, this is always the first thing to rule out.

  • The enchantment never appears at the enchanting table. Two common causes. First, you forgot to add it to the in_enchanting_table tag. Without that membership the table will never offer it, no matter how the definition reads. Second, its primary_items (or supported_items, if you left primary_items out) doesn’t include the item you’re trying to enchant. The table only offers an enchantment for items in its primary set.

  • The whole file fails to load (the enchantment disappears). An enchantment definition needs its required fields present and correctly shaped: a missing supported_items, a misspelled effect type, or an effect component whose inner effect object is malformed will make the game reject the file. Check the game log right after the world loads for the error, and compare your effects block field-by-field against the Enchantment definition page. Remember that when overriding a vanilla enchantment you must include every field, because the override replaces the entire file.


One more file type: enchantment_provider. There’s a sibling system worth knowing the name of, even though we won’t build one here. An enchantment provider is a small file that decides which enchantments (and at what levels) get rolled onto an item in a given situation. It’s the machinery behind tools like the loot function enchant_with_levels you met in Chapter 17, which enchants an item by experience level. If you ever need to author one, open the Enchantment provider page on the wiki, or copy a vanilla example and adapt the fields you find there. For most projects you won’t touch providers directly; you’ll lean on enchant_with_levels in a loot table and let it handle the selection for you.

Chapter 36 — Damage Types

Part X — Advanced: Data-Driven Systems. Like the rest of Part X, this chapter is à la carte: read it when you want it. It assumes you’re comfortable with registry tags (Chapter 14) and with the two item components that have been quietly waiting on it: blocks_attacks (Chapter 23) and damage_resistant (Chapter 24).

What You’ll Build

Back in Chapter 23 you turned items into shields with the blocks_attacks component, and two of its fields (bypassed_by and the type inside damage_reductions) pointed at something called a “damage type tag” that we promised to explain in Chapter 36. In Chapter 24 the damage_resistant component did the same thing with its types field. This is Chapter 36. By the end of it, you’ll know exactly what those fields were pointing at, and you’ll be able to make your own.

A damage type is a named kind of damage with its own properties: how much hunger it drains, whether it gets stronger on Hard difficulty, which hurt animation it shows, and what death message it writes in chat. The game’s own “arrow damage,” “lava damage,” and “fall damage” are all damage types, each defined in a small JSON file. In this chapter you’ll write your own damage type called void touch, learn the /damage command that applies it, group damage types with damage type tags, and use a tag to make your void-touch damage ignore armor entirely. Everything goes into the running mypack data pack from Chapter 9, and you’ll test it in the Chapter 8 test world.

What a damage type actually is

Here’s the plain definition: damage types are JSON files located in data packs that define different kinds of damage that entities can take. They control which attributes the damage has as well as which death message is used when an entity dies due to that type of damage.

So a damage type is data, just like a recipe or a loot table. It lives at a predictable path:

data/<namespace>/damage_type/<name>.json

That means it follows the same friendly rule as the other data-pack files you’ve written since Chapter 9: when you change one and run /reload, the game picks it up right away. (Contrast that with enchantments back in Chapter 35, which are a dynamic registry and need a full world reload to change. Damage types are the easy kind: edit, /reload, done.)

One important limit: custom damage types can be applied only by using the /damage command. A custom damage type doesn’t automatically attach itself to anything: no block, no mob, no item starts dealing it on its own. The only way to make your damage type actually hurt something is the /damage command, which you’ll meet in a moment. That’s by design: it means you decide exactly when your custom damage happens.

The damage type file, field by field

The damage type file has a small, friendly set of fields. Here is every one of them.

The file is a single JSON object (a { } with key-value pairs inside, like every data-pack file since Chapter 8). Its fields:

  • message_id — a string. It’s used as part of the death message translation key when an entity dies to this damage type and death_message_type is set to default (the default, see the field below). (A death message is the line like “Steve was slain by a zombie” that appears in chat when something dies.) More on this just below.
  • exhaustion — a number (it can have a decimal). The amount of hunger exhaustion this damage causes. Exhaustion is the hidden meter that, when it fills up, eats into your saturation and then your hunger bar, so a high exhaustion value makes this damage also make you hungry.
  • scaling — a string controlling whether the damage gets bigger on harder difficulties. It must be one of exactly three values:
    • never — the damage is always the same, whatever the difficulty.
    • always — the damage always scales with difficulty.
    • when_caused_by_living_non_player — it scales with difficulty only if the attacker was a living entity that wasn’t a player (so a zombie’s hit scales, but your own hit doesn’t).
  • effectsoptional. A string controlling how the hit is shown to the player: the little hurt animation and sound. One of: hurt (the default), thorns, drowning, burning, poking, or freezing. If you leave it off, you get hurt.
  • death_message_typeoptional. A string choosing which style of death message to use. One of: default (the default), fall_variants (uses the fall-damage messages), or intentional_game_design (the joke “intentional game design” message you’ve seen from beds in the Nether). If you leave it off, you get default.

That’s the whole format. Notice that only three fields really must be thought about (message_id, exhaustion, and scaling), and the game’s built-in minecraft:arrow damage type uses exactly those three:

{
"exhaustion": 0.1,
"message_id": "arrow",
"scaling": "when_caused_by_living_non_player"
}

That single object is the entire vanilla arrow-damage definition: a tenth of a point of exhaustion, the arrow message id, and difficulty scaling only when a non-player living thing fired it. Your own damage types will look just as short.

Under the Hood (skippable). How does message_id become a death message? When death_message_type is default (the standard message logic), the game builds a translation key from it. In the normal case it’s death.attack.<message_id>, so a message_id of arrow looks up death.attack.arrow. If the killer was holding a named item, it instead uses death.attack.<message_id>.item, and for an “assisted” death (the dying entity was recently hurt by something living) it uses death.attack.<message_id>.player. You don’t have to supply those translation strings; if you don’t, the game just shows the raw key. Writing your own death-message text means adding a language file, which is a resource-pack job covered in Chapter 30; here we only need the message_id field itself.

Applying your damage type: the /damage command

A damage type sitting in a file does nothing until you fire it. The tool for that is /damage. Here is its Java Edition syntax:

damage <target> <amount> [<damageType>] [at <location>]
damage <target> <amount> [<damageType>] [by <entity>] [from <cause>]

The square brackets mean “optional,” so the simplest form is just damage <target> <amount>. The arguments:

  • target — which entity (or entities, via a selector) to damage.
  • amount — how much damage to inflict.
  • damageTypeoptional. Which damage type to use. If not specified, it defaults to minecraft:generic. This is where your custom damage type’s id goes.
  • at <location>optional. Where the damage came from, for damage that wasn’t caused by an entity (like the position of an exploding bed).
  • by <entity>optional. The entity that dealt the damage.
  • from <cause>optional. The cause of the damage, for example the skeleton that shot the arrow, as opposed to the arrow itself.

There’s one caveat worth understanding before you’re confused in-game: the amount you type is not guaranteed to be the exact damage applied. The damage specified by the <damageType> argument is not the exact amount of damage that will be applied to the entity. The resulting damage is affected by statistics that would otherwise modify or nullify it. In plain terms: your /damage runs through all the normal damage math. If the target is wearing armor, armor still reduces it. If the type counts as fire and the target has Fire Resistance, it’s nullified. If a creative-mode player is the target, most damage does nothing. So /damage @s 6 minecraft:generic might land for less than 6 once armor and effects have their say, which is exactly the behavior we’re about to exploit with a tag.

Here’s a worked example. It makes a villager named villager_1 deal 1 point of damage to the nearest iron golem:

damage @e[type=iron_golem, sort=nearest, limit=1] 1 generic by @e[type=villager, limit=1, name="villager_1"]

Read it left to right: damage the nearest single iron golem, for 1, with the generic damage type, dealt by the named villager. Because the villager is named the by argument, a death message could even credit it.

Modern Minecraft. Older tutorials had no clean way to “just hurt that entity for N.” People faked it with instant-damage potion effects, or by briefly summoning harmful mobs. The /damage command (and data-driven damage types behind it) is the modern, direct way: one command, an exact source and type, full control.

Damage type tags: grouping kinds of damage

You already know registry tags from Chapter 14: a JSON file under data/<namespace>/tags/<registry>/ whose values array lists members, referenced elsewhere with a #. A damage type tag is simply that idea applied to the damage_type registry. A damage type tag is a group of damage types. It can be used when testing for damage type arguments with #<resource location>, which succeeds if the damage type matches any of the damage types specified in the tag.

So the file lives at:

data/<namespace>/tags/damage_type/<name>.json

…and you reference the whole group with a #, exactly as you referenced block and item tags in Chapter 14. The difference is what the members are: here, each member is a damage type.

Why do these matter? Because the game uses a long list of built-in damage type tags to decide how damage behaves. There are many; here are the ones you’re most likely to care about:

  • bypasses_armor — “Damage from these types ignores armor reduction.” (Our void-touch trick.)
  • bypasses_shield — “Damage from these types does not get blocked by shields.”
  • bypasses_effects — “bypasses any damage reduction (Resistance effect and enchantments).”
  • bypasses_resistance — bypasses the Resistance potion effect specifically.
  • bypasses_enchantments — bypasses enchantment-based damage reduction (like Protection).
  • is_fire — fire-type damage: ignored if fireDamage is off or the target has Fire Resistance, and reduced by Fire Protection.
  • is_explosion — reduced by the Blast Protection enchantment.
  • is_projectile — reduced by Projectile Protection; also used to decide if endermen teleport.
  • is_fall — fall damage: ignored if fallDamage is off or the target has Slow Falling, and reduced by Feather Falling.
  • witch_resistant_to — “reduce the amount of damage dealt to witches by 85%.”
  • wither_immune_to — “Prevents the Wither from taking these damage types.”

You don’t define these tags; the game ships them. What you do is add your own damage types to them. Want your custom damage to skip armor? Add it to bypasses_armor. Want a mob immune to it? That’s what wither_immune_to does for the wither. Tags are the bridge between “I made a new kind of damage” and “the game’s existing rules treat it correctly.”

Closing the loop: this is what Chapters 23 and 24 pointed at

Now we can finally settle the two promises from Part VI.

Back in Chapter 23 you built shield items with the blocks_attacks component, and two of its fields were left waiting on this chapter. The type inside each damage_reductions rule, and the bypassed_by field, were both described there as taking “a damage type tag.” Now you know exactly what that means and what to write: a #-prefixed damage type tag, the kind of file you just learned to reference. Put #minecraft:is_explosion in a damage_reductions rule’s type and that rule blocks explosions; put a tag in bypassed_by and any incoming hit whose type is in that tag ignores the shield entirely. Those fields were always pointing at the damage type tags this chapter defines.

Chapter 24 did the same with the damage_resistant component. Recall the fireproof cake you made there:

/give @s cake[damage_resistant={types:"#minecraft:is_fire"}]

Its types field was described as “a damage type tag prefixed with #,” and now you can see what that value really is: #minecraft:is_fire is the built-in is_fire damage type tag, the group of all fire-related damage types. The cake shrugs off every member of that group at once. That’s the whole point of tags, and the reason both components take a tag rather than a single type: one #name stands in for a whole family of damage types, so a single field can cover “all fire damage” or “all explosions” without listing each one.

So every “damage type tag” the earlier chapters waved at is the same thing: a data/<namespace>/tags/ damage_type/<name>.json file (or one of the built-in ones), referenced with #.

Walkthrough: the “void touch” damage type that bypasses armor

Let’s build it. The goal: a damage type called mypack:void_touch that hurts no matter what armor the target is wearing.

Step 1 — Write the damage type

Create the file. We’ll give it a little exhaustion, no difficulty scaling, and the freezing hurt effect so it feels eerie:

mypack/data/mypack/damage_type/void_touch.json

{
  "message_id": "void_touch",
  "exhaustion": 0.1,
  "scaling": "never",
  "effects": "freezing",
  "death_message_type": "default"
}

Every field here is one we listed earlier: message_id names it (and feeds the death.attack.void_touch translation key), exhaustion of 0.1 matches vanilla arrow damage, scaling: "never" keeps it constant on every difficulty, effects: "freezing" borrows the icy hurt shake, and death_message_type: "default" uses the normal message logic.

Step 2 — Make it bypass armor with a tag

On its own, void_touch is just ordinary damage that armor would soften. To make it ignore armor, we add it to the game’s built-in bypasses_armor damage type tag. Because we’re extending a vanilla tag, the file goes in the minecraft namespace (this is the Chapter 14 trick for adding to a vanilla tag), and we leave replace out (or set it false) so we add to the vanilla tag instead of wiping it:

mypack/data/minecraft/tags/damage_type/bypasses_armor.json

{
  "values": [
    "mypack:void_touch"
  ]
}

This is the same values-array tag format from Chapter 14. The only new thing is the registry folder name, damage_type. Now mypack:void_touch is a member of bypasses_armor, and damage from anything in that tag ignores armor reduction. Full plate or nothing, the void touch lands the same.

Under the Hood (skippable). Why a separate tag file instead of a field on the damage type that says “ignore armor”? Because “ignore armor” isn’t a property of the damage type itself; it’s a rule the game applies to a group. Lots of different damage types might want to bypass armor; rather than repeat a flag on each, the game keeps one bypasses_armor list and checks membership. This is the same reason damage_resistant and blocks_attacks take tags, not single types: one tag, many members, one rule.

Step 3 — A function to test it

Let’s summon a target and zap it. We’ll spawn a zombie (which can wear armor, making the armor-bypass visible) a few blocks in front of us, then hit it. Following our rule from Chapter 9, there’s no leading slash inside a function file:

mypack/data/mypack/function/void_touch.mcfunction

# spawn a test zombie 3 blocks in front of where the function runs
summon zombie ^ ^ ^3 {CustomName:'"Void Test"'}
# hit the nearest zombie for 6 with our custom damage type
damage @e[type=zombie, sort=nearest, limit=1] 6 mypack:void_touch
say Void touch applied!

Save everything, then in-game:

/reload
/function mypack:void_touch

A zombie named “Void Test” appears ahead of you and immediately takes 6 points of void-touch damage with the icy freezing flash. Because void_touch is in the bypasses_armor tag, that 6 lands in full even if the zombie spawns wearing armor. Try it next to a normal hit and you’ll see armored mobs take the void touch just as hard as bare ones.

Modern Minecraft. This whole flow (define a kind of damage in a file, group it with a tag, apply it with one command) didn’t exist in older versions, where damage types were hardcoded. Being able to ship a brand-new damage type in a data pack, and slot it into the game’s existing armor/shield/resistance rules just by adding it to the right tag, is a genuinely modern capability.

Figure (to be captured). a player running /function mypack:void_touch; a zombie named “Void Test” ahead taking the freezing hurt flash, with the “Void touch applied!” chat line

Practice

  1. A gentler touch. Make a second damage type, mypack:soft_touch, that’s identical but does not go in bypasses_armor. Find or spawn an armored mob (zombies sometimes spawn wearing armor; or test on yourself wearing armor in Survival), hit it with both, and watch the armored target take less from soft_touch than from void_touch. This shows the tag (not the damage type file) is what bypasses armor.

  2. Borrow a vanilla feeling. Change your void_touch file’s effects field to burning instead of freezing, /reload, and run the function again. Same damage, different on-screen reaction. Try each of the legal values (hurt, thorns, drowning, burning, poking, freezing) and notice that effects only changes the look and sound, not the numbers.

  3. Scaling test. Make a mypack:hard_hit damage type with "scaling": "always". Apply it with /damage on Easy, then switch your world to Hard and apply it again with the same amount. Because it scales with difficulty, the harder setting should hurt more. (Set difficulty from the game menu or with the difficulty command.)

  4. Make a shield ignore it. Revisit your Chapter 23 shield item. Build a blocks_attacks item whose bypassed_by field is a #-prefixed damage type tag that contains mypack:void_touch (you can make your own tag file, e.g. mypack:voidish, with void_touch in its values). Block with the item while a function hits you with void_touch, and confirm the block does nothing, because the tag is in bypassed_by. You’ve now wired your Chapter 23 component to your Chapter 36 tag with no missing pieces.

What Can Go Wrong

What Went Wrong? My /damage “worked” but the target barely lost health. Remember the caveat: the amount you type is not the final damage; it runs through normal reductions. Armor, Resistance, Protection enchantments, and Fire Resistance can all soften or cancel it. If you want it to ignore one of those, add your damage type to the matching built-in tag (bypasses_armor, bypasses_resistance, bypasses_enchantments, and so on). And note that a creative-mode target is immune to most damage entirely. Test in Survival.

What Went Wrong? The game says my damage type is an unknown damage type. Two usual causes. First, the file path must be exactly data/<namespace>/damage_type/<name>.json (singular damage_type, just like the singular function, recipe, and loot_table folders from earlier chapters). Second, you must reference it by its full id in /damage: mypack:void_touch, not just void_touch. After fixing either, run /reload and try again.

What Went Wrong? I added my type to bypasses_armor but it wiped out all the vanilla armor-bypassing damage. You almost certainly put "replace": true in the tag file, or you put the tag in your own namespace instead of minecraft. To extend the vanilla bypasses_armor tag, the file must be at data/minecraft/tags/damage_type/bypasses_armor.json and must leave replace out (it defaults to false). That merges your type into the existing list, the extend-vs-replace rule you learned in Chapter 14.

Chapter 37 — Advanced Loot Tables

What You’ll Build

Back in Chapters 16 and 17 you learned the everyday half of loot tables: pools that get rolled, entries with weights that decide which item wins a roll, conditions that gate a pool or entry, and functions that modify the dropped item. That’s enough to make a zombie drop a custom sword or a chest fill with random treasure.

This chapter adds the advanced half: the parts that let a single loot table make real decisions. By the end you’ll have a custom block in mypack that drops different things depending on the enchantments on the tool that mined it: bare-handed or with a plain pickaxe you get a chunk of raw material, with Fortune you get more of it, and with a special enchantment you get a rare shard that copies data straight off the block. Along the way you’ll learn entry types that choose between children, functions that copy data off the thing being looted, a function that calls a saved item modifier by name, the full list of loot context types (what data each situation hands your loot table), a condition that reads a scoreboard, and the tools that scale drops by enchantment level.

This chapter extends your mypack pack from Chapter 9 and the test world from Chapter 1. It assumes the loot-table structure from Chapter 16 (pools, entries, weight, number providers), the conditions and functions from Chapter 17 (match_tool, set_name, set_components, enchanted_count_increase), predicates from Chapter 18 (all_of/any_of/inverted and the reference condition), data components from Chapter 21, and scoreboards from Chapter 11. Every new keyword below is copied straight from the wiki so the spelling is exactly right.

Composite entries: choosing between children

In Chapter 16 every entry you wrote was a singleton entry: one entry, one weighted chance to drop one kind of item (item, tag, loot_table, empty). The other kind is a composite entry: an entry that doesn’t get rolled itself, but instead expands into child entries before the pool is rolled. Composite entries first expand their children, and then singleton entries that do not meet their conditions are removed from the pool, and the entries are rolled afterward.

A composite entry has a children field (the list of entries it expands into) instead of a name. There are three composite types:

  • group — “All entries in the children are extracted.” A plain bundle. Handy when one condition should apply to several entries at once: put the condition on the group and it gates the whole batch.
  • alternatives — “Extracts only the first successful (conditions are met) entry. Conditions are checked in order.” This is an if / else-if / else for loot: the first child whose conditions pass wins, and the rest are skipped.
  • sequence — “Extracts the child entries in sequential order, continuing until an entry’s conditions fail, then no more children are extracted.” This drops a run of children: every one from the top until the first failure.

Here’s the shape of an alternatives entry. Read it as “drop a diamond if a player’s tool has Silk Touch; otherwise drop coal”:

{
  "type": "minecraft:alternatives",
  "children": [
    {
      "type": "minecraft:item",
      "name": "minecraft:diamond",
      "conditions": [
        {
          "condition": "minecraft:match_tool",
          "predicate": { "enchantments": [ { "enchantments": "minecraft:silk_touch" } ] }
        }
      ]
    },
    {
      "type": "minecraft:item",
      "name": "minecraft:coal"
    }
  ]
}

The first child has a condition; the second has none, so it always passes. alternatives checks them top to bottom and keeps the first that passes, exactly the “else” behaviour you’d want.

Under the Hood (skippable). Why is a composite entry not just rolled like a normal entry? Because weights only make sense between things competing in the same pool. A composite entry flattens into the pool first (its surviving children become ordinary weighted singletons), and then the pool rolls. So alternatives decides which children exist, and weight still decides which of the survivors wins the roll.

dynamic and tag, revisited

Two more entry types round out the advanced set. You met tag briefly in Chapter 16; the other, dynamic, is new here.

The dynamic entry “Drops block-specific loot.” Its name field can be contents (which drops the items in a shulker box) or sherds (which drops the sherds of a decorated pot). It “Does not work for other blocks,” so it’s a narrow tool: it exists so a shulker box drops what’s inside it and a decorated pot drops its sherds.

{
  "type": "minecraft:dynamic",
  "name": "contents"
}

The tag entry drops items from an item tag. Its behaviour flips on a boolean expand field: if expand is false it’s a single entry that “drops all items in the tag”; if expand is true it becomes a composite entry that “provides one singleton entry per item in the tag with the same weight and conditions.” It spreads the tag’s items across the pool as separate weighted options. (One caveat worth knowing: when expand is true, item modifiers attached to it “do not work due to a bug.”)

Reusing whole loot tables and saved modifiers

You already have two ways to reuse loot you built elsewhere; this section just lines them up.

The loot_table entry (Chapter 16) drops the loot from another loot table. Its field is value, the loot table to be used. The wiki warns: it “Cannot be the ID of the current loot table file. Recursive calling is not allowed.” So a chest table can pull in a shared “common junk” table without copy-pasting it.

{
  "type": "minecraft:loot_table",
  "value": "mypack:common_junk"
}

The matching tool on the function side is the reference function. From the Item modifier page: reference — “Call sub-functions,” with a name field that is the “Location of function to call.” In plain terms, you save an item modifier as its own file (you learned standalone item modifiers in Chapter 20) and then call it by name from inside a loot table’s functions list, instead of pasting the whole modifier in. One saved modifier, reused everywhere.

{
  "function": "minecraft:reference",
  "name": "mypack:name_mystic_shard"
}

Modern Minecraft. Don’t confuse this reference function with the reference condition from Chapter 18. They share a name and a name field but do different jobs: the condition (in a conditions list) invokes a saved predicate and returns pass/fail; the function (in a functions list) invokes a saved item modifier and applies it to the item. Same idea (“call something I saved elsewhere”) applied to two different kinds of saved file.

Copy functions: pulling data off the source

Sometimes the item you drop should carry data from the thing it came from: the block that broke, the mob that died. Two functions do this.

copy_components — “Copies components from a specified source onto an item.” Its fields are:

  • source — “Source type to pull from. Specifies an entity or block entity from loot context.” The wiki lists the allowed values: block_entity, this, attacker, direct_attacker, attacking_player, target_entity, interacting_entity, tool.
  • include — optional, “A list of components to include. If omitted, all components are copied.”
  • exclude — optional, “A list of components to exclude.”

So copy_components with "source": "block_entity" copies data components off the broken block entity onto the dropped item. That’s how, for example, a broken block can drop an item that remembers a stored component.

{
  "function": "minecraft:copy_components",
  "source": "block_entity",
  "include": [ "minecraft:custom_name" ]
}

The second is copy_custom_data. The wiki describes it as: “Copies NBT values from an entity, block entity, or storage to the item’s minecraft:custom_data component.” This is the modern, correctly-named function for copying raw NBT. If you’ve seen old tutorials call it copy_nbt, this is its current name. Its source (shorthand form) names the thing to copy from, and an ops list gives the copy operations, each with a source NBT path, a target path (relative to the item’s minecraft:custom_data), and an op set to replace, append, or merge.

{
  "function": "minecraft:copy_custom_data",
  "source": "block_entity",
  "ops": [
    { "source": "Owner", "target": "miner", "op": "replace" }
  ]
}

Modern Minecraft. In Chapter 21 you learned that data components replaced most raw NBT. That’s why there are two copy functions. copy_components is the modern, component-aware one you’ll reach for most; copy_custom_data is for the cases where you genuinely need to move loose NBT into the catch-all minecraft:custom_data component. Prefer copy_components unless you specifically need raw NBT.

Loot context types: what data each situation gives you

In Chapter 17 you met loot context as the reason killed_by_player and match_tool only work on mob and block tables: a loot table only has the parameters its situation supplies. This section deepens that. Loot context is “a set of parameters available to loot tables, predicates, item modifiers, and number providers,” and the check is done “when the data pack is loaded, rather than at runtime.”

That check is driven by the loot table’s type field (Chapter 16’s root field). The wiki: type “Specifies the loot context in which the loot table should be invoked. All item modifiers, predicates and number providers are then validated to ensure the parameters of the context type specified here cover all requirements, and prints a warning message in the output log if any modifier or predicate requires a context parameter that is not covered.” In other words, declaring the right type lets the game catch your mistakes at load time. If you ask for the killer in a chest table, you get a warning, not a silent failure later.

Different situations provide different parameters. Three you’ll care about most:

  • A chest / container being opened provides an Origin (the centre of the chest) and a this entity (the entity that opened it). No tool, no killer.
  • A living entity’s death (a mob dying) provides the this entity (the one that died), an Origin, a Damage source, and three attacker entities: attacker (“the source of the final damage”), direct_attacker (“the entity that directly contacted the victim”), and attacking_player (“the player that most recently damaged the victim”). This is why killed_by_player, which checks for that attacking_player, works on mob tables.
  • Mining a block provides a Block state (“the block that was broken”), an Origin, a Tool (“the tool used to mine the block”), a this entity (the player who mined it), and a Block entity (“any block entity data of the block that was broken, if it was a block entity”). This is the context your custom-block table runs in, and the reason match_tool and Fortune scaling are available to it.

Two special type values are worth naming. "type": "empty" “means no context parameters can be used in this loot table”; it’s the do-nothing table. "type": "generic" (the default if you omit type) “means no checking for context parameters in this loot table when loading the data pack,” so it skips the load-time check entirely. Declaring a specific type is better when you can, precisely because it turns on that helpful warning.

Try It! Make a deliberate mistake to see the safety net work. In a block-mining table, add a killed_by_player condition (which needs the attacking_player the mob-death context provides, not the block context). With the table’s type set correctly for block mining, the game should warn you in the output log when the pack loads, before you ever break the block.

entity_scores: reading a scoreboard in a loot condition

Chapter 17’s conditions tested the world (was it a player kill? what tool?). The entity_scores condition reaches into the scoreboard system from Chapter 11. The wiki: it “Checks the scoreboard scores of an entity.” Its fields:

  • entity — “The entity to check. Specifies an entity from loot context” (so this, attacker, and so on — only the entities the current context provides).
  • scores — “Scores to check. All specified scores must pass for the condition to pass.” Each key is a scoreboard objective; the value is either a { "min": ..., "max": ... } range or, in the shorthand form, “a single number” the score must equal.

So you can make a drop happen only when a player’s score is in range. For example, only reward a block’s special loot once the player has reached a quest milestone you track on a scoreboard objective.

{
  "condition": "minecraft:entity_scores",
  "entity": "this",
  "scores": {
    "mypack_quest": { "min": 5 }
  }
}

This reads: pass only if the this entity’s mypack_quest objective is at least 5. Because it needs the entity from loot context, it “always fails if not provided,” so the context’s type has to actually supply that entity.

Scaling drops by enchantment: table_bonus and apply_bonus

The last pieces are the tools that make drops respond to Fortune and similar enchantments: the way mining gravel with Fortune drops flint more often, or an ore drops more ingots. There are two, and they do different jobs.

table_bonus is a condition. The wiki: it “Passes with probability picked from a list, indexed by enchantment power. Requires tool provided by loot context. If not provided, the enchantment level is regarded as 0.” Fields:

  • enchantment — the resource location of the enchantment.
  • chances — “List of probabilities for enchantment power, indexed from 0.”

So chances: [0.1, 0.5, 1.0] means: with the enchantment at level 0 the entry passes 10% of the time, at level 1 it’s 50%, at level 2 it’s guaranteed. It’s a chance gate that improves with the enchantment, perfect for a rare extra drop.

{
  "condition": "minecraft:table_bonus",
  "enchantment": "minecraft:fortune",
  "chances": [ 0.1, 0.14, 0.25, 1.0 ]
}

apply_bonus is a function: it scales the item count. The wiki: it “Applies a predefined bonus formula to the count of the item stack.” Fields:

  • enchantment — the enchantment “used for level calculation.”
  • formula — a resource location. The wiki lists three: ore_drops (“a special function used for ore drops in the vanilla game”), uniform_bonus_count (uniform distribution from 0 to level * bonusMultiplier), and binomial_with_bonus_count.
  • parameters — “Values required for the formula” (e.g. bonusMultiplier for uniform_bonus_count; extra and probability for the binomial one).
{
  "function": "minecraft:apply_bonus",
  "enchantment": "minecraft:fortune",
  "formula": "minecraft:ore_drops"
}

That single function is exactly how vanilla ores multiply their drops with Fortune. Drop it into your own block’s loot table and your block behaves like an ore.

Modern Minecraft. Chapter 17 taught enchanted_count_increase for the Looting bonus on mob drops. apply_bonus is its cousin for mining: same idea (more drops per enchantment level), but it runs in the block-mining context and reads the tool’s enchantment instead of the killer’s. Use enchanted_count_increase for mob loot, and apply_bonus for block loot.

Walkthrough: a tool-aware custom block drop

Time to put it together. You’ll write a loot table for a custom “mystic ore” block. The plan:

  1. If the tool has Silk Touch, drop the block itself (one mystic ore).
  2. Otherwise, drop a raw material, and if the tool has Fortune, drop more of it (via apply_bonus), plus a chance at a rare bonus shard (via table_bonus).
  3. The rare shard gets a custom name applied through a saved item modifier called with reference.

First, the saved modifier the table will call. Create this file:

mypack/data/mypack/item_modifier/name_mystic_shard.json

{
  "function": "minecraft:set_name",
  "name": { "text": "Mystic Shard", "italic": false, "color": "aqua" },
  "target": "custom_name"
}

This is a standalone item modifier (Chapter 20): a single set_name function that gives the item the custom name “Mystic Shard” (Chapter 17 taught set_name with its target of custom_name).

Now the loot table itself. Notice the type at the top: minecraft:block declares the block-mining context, which turns on load-time checking and gives us the tool parameter that match_tool, table_bonus, and apply_bonus all need.

mypack/data/mypack/loot_table/blocks/mystic_ore.json

{
  "type": "minecraft:block",
  "pools": [
    {
      "rolls": 1,
      "entries": [
        {
          "type": "minecraft:alternatives",
          "children": [
            {
              "type": "minecraft:item",
              "name": "minecraft:diamond_ore",
              "conditions": [
                {
                  "condition": "minecraft:match_tool",
                  "predicate": {
                    "enchantments": [ { "enchantments": "minecraft:silk_touch" } ]
                  }
                }
              ]
            },
            {
              "type": "minecraft:item",
              "name": "minecraft:diamond",
              "functions": [
                {
                  "function": "minecraft:apply_bonus",
                  "enchantment": "minecraft:fortune",
                  "formula": "minecraft:ore_drops"
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "rolls": 1,
      "conditions": [
        {
          "condition": "minecraft:table_bonus",
          "enchantment": "minecraft:fortune",
          "chances": [ 0.0, 0.25, 0.5, 1.0 ]
        }
      ],
      "entries": [
        {
          "type": "minecraft:item",
          "name": "minecraft:amethyst_shard",
          "functions": [
            {
              "function": "minecraft:reference",
              "name": "mypack:name_mystic_shard"
            }
          ]
        }
      ]
    }
  ]
}

Walk through what happens when the block breaks:

  1. First pool, alternatives entry. Its first child drops diamond_ore only if match_tool sees Silk Touch. If Silk Touch is present, that child wins and the second is skipped, so you get the ore block back. If not, the second child (no condition, always passes) wins and drops a diamond, whose count is scaled by apply_bonus reading Fortune, so plain tools give one and Fortune gives more.
  2. Second pool only runs at all when its table_bonus condition passes, and that chance climbs with Fortune (0.0 at level 0 means never without Fortune; 1.0 at level 3 means always). When it passes, it drops an amethyst_shard and calls your saved name_mystic_shard modifier through reference, so the shard arrives already named “Mystic Shard.”

To use this table on an actual block in-game you’d assign it the way Chapter 17 assigned a table to zombies, by overriding the block’s loot table file (here we’ve put it at a mypack: path so you can test it with the loot command first). Try it from a function:

mypack/data/mypack/function/test_mystic_ore.mcfunction

loot give @s loot mypack:blocks/mystic_ore

Figure (to be captured). running test_mystic_ore with a Fortune III pickaxe in hand and seeing extra diamonds plus a named Mystic Shard appear

Because loot give ... loot uses the chest/command context rather than a real block break, the surest test is to bind the table to a real block and mine it. But the command is a quick first check that the file parses and the named shard appears.

Practice

These extend the block you just built. Keep working in the same file unless told otherwise.

  1. A coal consolation prize. Add a third child to the alternatives entry (after the Silk Touch and Fortune children) so there’s always something. Since alternatives keeps the first passing child, where in the children list must a no-condition fallback go for it to act as the “else”?

  2. Score-gated jackpot. Add a third pool that drops a minecraft:nether_star, gated by an entity_scores condition on the this entity requiring an objective mypack_mining_level of at least 10. (You set up scoreboard objectives in Chapter 11.) Remember entity_scores needs the entity from loot context, which the block-mining type provides as this.

  3. Copy the block’s name onto the drop. If your mystic ore is a block entity that can hold a custom name, add a copy_components function ("source": "block_entity", "include": ["minecraft:custom_name"]) to the diamond entry so a renamed block hands its name to its drop.

  4. Try It! (a sequence). Replace one pool’s single entry with a sequence composite entry whose children each have a table_bonus condition at increasing levels. Watch how sequence drops a run (every child from the top until one fails) versus alternatives, which keeps only the first that passes.

What Can Go Wrong

The wrong context type, or a missing parameter. If you set "type": "minecraft:chest" on this table, the chest context provides no Tool, so match_tool, table_bonus, and apply_bonus have nothing to read. Conditions that require a missing parameter “always fail if not provided,” and the load-time check warns you in the output log. Fix: use "type": "minecraft:block" for a block’s loot, so the Tool and Block state parameters exist.

Confusing table_bonus with apply_bonus. They’re easy to swap because both involve an enchantment. Remember: table_bonus is a condition (it goes in a conditions list and decides whether an entry/pool runs, by chance); apply_bonus is a function (it goes in a functions list and changes the count). Putting one where the other belongs makes the file fail to load.

Expecting copy_nbt to exist. Old tutorials reference a function named copy_nbt. In current Java Edition the function is copy_custom_data (it copies NBT into the minecraft:custom_data component), and for components you usually want copy_components instead. Use the current names or the loot table won’t parse.

A reference that points at the wrong kind of file. The reference function must name an item modifier file; the reference condition must name a predicate file. Point a function at a predicate (or vice-versa) and it won’t resolve. Double-check which list (functions or conditions) your reference sits in.

Chapter 38 — Villager Trades and Other Data-Driven Registries

What You’ll Build

Way back in Chapter 7 you learned that a data pack works by adding files to registries (the game’s master lists of one kind of thing each) and that some registries are dynamic, meaning data packs are allowed to add to them. Since then you’ve filled a lot of those lists: the recipe registry, the loot table registry, the advancement registry, the tag registries. This chapter pulls back and shows you the whole map, the full folder list of every registry a data pack can write to, and then teaches the most interesting new one in modern Minecraft from top to bottom.

By the end you’ll have read the complete registry map, and you’ll have built a working sulfur_cube_archetype: a JSON file that defines how a sulfur cube behaves, whether it floats, what it eats, how hard it hits, and whether it explodes. You’ll attach that behavior to an item, add a custom banner pattern definition, and take a guided tour of how villager trades became data-driven (the villager_trade and trade_set folders, grouped by trade tags). You’ll also learn something just as important: which registries the book can teach you to fill in today, and which ones the game only names, so you know exactly where to look when you need them.

Figure (to be captured). a custom sulfur cube bouncing in water next to a banner showing a custom forge-mark pattern

The Registry Map

Open your data pack’s data/mypack/ folder in your mind. Every folder you’ve made so far (function, recipe, loot_table, advancement, tags/) sits at the same level. Each one is a registry folder: a folder named after a registry, whose .json files become entries in that registry.

Registry folder. A folder under data/<namespace>/<registry name>/. The rule is exact: the file data/<namespace>/<registry name>/<path>.json is loaded into the registry name registry with ID <namespace>:<path>. So a file at data/mypack/recipe/forge_blade.json becomes the recipe mypack:forge_blade. The folder name is the registry name. (Both the registry name and the path can contain slashes, which just makes extra sub-folders.)

That one rule is the whole secret of data packs. You already know it in your hands; now you’ll see the full list of folder names you’re allowed to use. Here is the registry folder list, straight from the Data pack page (worldgen folders are grouped at the bottom and belong to Part XI):

data/<namespace>/
  function                  .mcfunction files with lists of commands
  structure                 .nbt files defining a saved structure of blocks
  tags/                     collections of things (one sub-folder per registry)
  advancement               definitions of advancements
  banner_pattern            * textures and names to use for banner patterns
  cat_variant               * textures and spawn conditions of cat variants
  chat_type                 * formatting of chat messages
  chicken_variant           * textures and spawn conditions of chicken variants
  cow_variant               * textures and spawn conditions of cow variants
  damage_type               * attributes of damage and death messages
  dialog                    * definitions of dialogs            (Chapter 39)
  dimension                 * biome layout and terrain of dimensions
  dimension_type            * properties of dimensions
  enchantment               * enchantment effects, supported items, level cost, etc.
  enchantment_provider      * selection of enchantments for specific uses
  frog_variant              * textures and spawn conditions of frog variants
  instrument                * instruments for goat horns
  item_modifier             loot functions used to modify items
  jukebox_song              * jukebox song definitions
  loot_table                loot from mobs, blocks, chests, etc.
  painting_variant          * size and texture of paintings
  pig_variant               * textures and spawn conditions of pig variants
  predicate                 tests for specific conditions
  recipe                    recipes for crafting, smelting, etc.
  sulfur_cube_archetype     * defines Sulfur Cube archetypes
  test_environment          * groups GameTests with their preconditions
  test_instance             * a test the GameTest framework can run
  timeline                  * events/attributes according to the time of day
  trade_set                 * a set of trades selected by villagers / wandering traders
  trial_spawner             * configuration of trial spawners
  trim_material             * colors, ingredients, name of trim materials
  trim_pattern              * textures and name of trim patterns
  villager_trade            * trades of villagers and wandering traders
  wolf_sound_variant        * sound variants of wolves
  wolf_variant              * textures and spawn conditions of wolf variants
  world_clock               * clocks used to keep track of internal time
  worldgen/                 * the world-generation registries          (Part XI)

That little red * is doing real work. Let’s talk about it.

Experimental-settings folder (the *). Some folders are marked with an asterisk because having a valid file inside any of them will mark the data pack as using experimental settings. A pack that uses experimental settings shows a warning screen when you open the world in singleplayer, and it cannot be uploaded to Realms. For example: defining a custom instrument inside the instrument/ folder counts as experimental, but doing the same thing through item components does not.

So most of the shiny new registry folders in that list (including sulfur_cube_archetype) are experimental folders. That’s not a reason to avoid them. It just means: expect the warning screen, and remember the rule below in What Can Go Wrong about reloading them.

Modern Minecraft. Older tutorials talk about “experimental features” as if they’re half-broken betas you toggle on. In modern Minecraft, an experimental setting is simpler than that: it’s just a flag the game raises because your pack put a file in one of these folders. The reason is precise: internally, most experimental settings use dynamic registries, and dynamic registries can’t be hot-reloaded (more on that at the end of the chapter). The feature itself is shipped and real; the label is about how the game loads it, not about whether it works.

Trades Are Data-Driven Now

Here’s a change worth pausing on. For most of Minecraft’s life, what a villager would sell you was baked into the program. Look at the registry map again: there are now three trade-related entries, villager_trade, trade_set, and (under tags/) villager trade tags.

Modern Minecraft. If you followed an old guide that summoned villagers with giant Offers NBT blobs to fake custom trades, that still describes the runtime shape: when the trade menu is first opened, the game generates an Offers compound holding a Recipes list, where each recipe has a buy cost item, an optional buyB second cost, a sell item, a maxUses, and so on. But you no longer have to hand-build that. The trades themselves are now data pack files.

The three pieces fit together like this, and the Data pack folder list tells us what each one is for:

  • villager_trade (the villager_trade/ folder) — “Trades of villagers and wandering traders.” One file describes one trade offer.
  • trade_set (the trade_set/ folder) — “A set of trades selected by villagers and wandering traders.” A group of trades chosen together.
  • villager trade tag (under tags/) — the grouping and selection layer. In one line: a villager trade tag is a group of villager trades.

Villager trade tag. A tag, exactly like the block and function tags you built in Chapter 14 (a .json file with a values list), that groups villager trades together. The vanilla trade tags follow a clear pattern: one tag per profession and level, like armorer/level_1, cleric/level_3, farmer/level_5, plus special ones such as common_smith/level_1 (shared smith trades) and wandering_trader/common. A villager who is a level-3 cleric draws its offers from the cleric/level_3 trade tag.

So the modern pipeline is: you write trade offers as villager_trade (and group them with trade_set), and a trade tag like #minecraft:villager_trade/cleric/level_3 decides which villager gets them. To add a cleric trade, you’d add your trade to that tag: the same “extend a vanilla tag without replacing it” move you learned in Chapter 14.

One thing this chapter won’t do is make you memorize the exact JSON field names inside a villager_trade or trade_set file. They’re new 26.x registries, and a file format like that is the kind of detail you should read off a reference each time, not carry in your head. So when you build custom trades for real, open the villager_trade / trade_set page on the live wiki (or copy a vanilla example file) and write the field names you find there.

What you can carry in from here is the shape of the idea. The Villager page’s runtime Offers/Recipes data (buy, buyB, sell, maxUses, priceMultiplier, demand, rewardExp, xp) tells you what a trade contains conceptually: a cost (sometimes two), a result, and limits on how often it can be used. That’s the entity’s saved NBT rather than the pack file, so don’t copy it field-for-field, but it’s a faithful mental model of what your trade file will need to express.

That’s the working rule for this whole chapter, and it’s the one that keeps your packs correct: when a registry is brand-new and its file format isn’t settled documentation yet, this book teaches you what the folder is for and points you at the reference for the exact fields, rather than guessing them. Now let’s build the one new registry we can walk all the way through.

Walkthrough: A Sulfur Cube Archetype

The sulfur cube is a 26.x entity, and it’s the perfect teaching example because its whole behavior lives in a data pack file. Sulfur cubes use “archetypes” to define their behavior, and those archetypes are stored as JSON files within a data pack in the path data/<namespace>/sulfur_cube_archetype.

sulfur_cube_archetype. A registry file defining how a sulfur cube of that archetype behaves: what attributes it has, whether it floats, what it eats, how it damages things it touches, whether it explodes, how knockback affects it, and what sounds it makes. The file’s ID is <namespace>:<path>, just like every other registry entry.

Let’s build one called mypack:bouncing_bomb: a cube that floats in water, eats gunpowder, lightly shoves anything it touches, and explodes when ignited. Here is the complete file. Every field in it comes straight from the JSON format for this registry, and after the listing we’ll walk through each one.

mypack/data/mypack/sulfur_cube_archetype/bouncing_bomb.json

{
  "attribute_modifiers": [
    {
      "attribute": "minecraft:max_health",
      "id": "mypack:bouncing_bomb_health",
      "amount": 4.0,
      "operation": "add_value"
    }
  ],
  "buoyant": true,
  "contact_damage": {
    "amount": 2.0,
    "attribute_to_source": true,
    "damage_type": "minecraft:mob_attack"
  },
  "explosion": {
    "causes_fire": false,
    "fuse": 30,
    "power": 3
  },
  "items": "minecraft:gunpowder",
  "knockback_modifiers": {
    "horizontal_power": 1.5,
    "vertical_power": 1.0
  },
  "sound_settings": {
    "hit_sound": "minecraft:entity.tnt.primed",
    "push_sound": "minecraft:block.sand.step",
    "push_sound_cooldown": 0.5,
    "push_sound_impulse_threshold": 0.1
  }
}

Now the field-by-field tour. The root object has these seven keys:

  • attribute_modifiers — “A list of attribute modifiers to apply to sulfur cubes of this archetype.” You met attribute modifiers on items back in Chapter 23, and the shape here is the same family: each modifier is an object with an attribute (the id of the attribute to modify, here minecraft:max_health), a unique id for the modifier, an amount, and an operation. There are three operations: add_value, add_multiplied_base, and add_multiplied_total. We used add_value to give the cube +4 health.

  • buoyant — a true/false value: “Whether or not a sulfur cube of this archetype floats in liquids.” We set it true, so our bomb bobs on top of water instead of sinking.

  • contact_damage — this one is optional: if present, sulfur cubes of this archetype will damage entities on contact. Inside it: amount (the damage caused, here 2.0, one heart), attribute_to_source (whether the damage is attributed to the sulfur cube, which affects who “killed” the victim), and damage_type (which damage type to use; we picked minecraft:mob_attack).

  • explosion — also optional: “if present, sulfur cubes of this archetype can explode when ignited.” Three fields: causes_fire (true/false: does the blast light fires; we said no), fuse (the fuse time in game ticks; 30 ticks is 1.5 seconds), and power (the power of the explosion; 3 is roughly creeper-sized).

  • items — “An item or an item tag containing all items that can be fed to sulfur cubes of this archetype.” We gave a single item, minecraft:gunpowder. Because it accepts an item tag too, you could instead write "#minecraft:coals" (a tag, with the # you learned in Chapter 14) to let the cube eat any coal-like item.

  • knockback_modifiers — “Modifiers to the knockback received by sulfur cubes of this archetype,” with horizontal_power and vertical_power. These scale how far the cube gets shoved when it’s hit; 1.5 horizontal makes it skittish and easy to push around.

  • sound_settings — the sounds the cube makes, with four fields: hit_sound (a sound event played when the cube is hit), push_sound (played when it’s pushed), push_sound_cooldown (the cooldown for the push sound, in seconds), and push_sound_impulse_threshold (the smallest impulse needed to trigger the push sound). The two sound fields take sound-event ids of the kind you worked with in Chapter 30.

Save the file and /reload. Because sulfur_cube_archetype/ is an experimental folder, you may see the experimental-settings warning when you open the world. That’s expected (and there’s a reloading nuance in What Can Go Wrong). Your archetype now exists in the registry as mypack:bouncing_bomb, ready for any sulfur cube assigned to it.

Under the Hood (skippable). The wiki itself is still pinning down the precise in-game effect of some of these settings: exactly which behavior each archetype setting controls is not fully nailed down yet. The fields are documented and correct (that’s what we built); the exact gameplay feel of, say, a particular knockback_modifiers value is the kind of thing you confirm by testing in-game. That’s normal for a brand-new feature.

Putting the Cube Inside an Item

A sulfur cube can hold an item, and there’s an item component for that: sulfur_cube_content.

sulfur_cube_content. An item component storing “the item stored inside the sulfur cube.” The game adds gray italic tooltip text reading “Contains: <item>” on an item, and on a sulfur cube entity it doubles as the body armor slot. It’s the bridge between an ordinary item and the sulfur cube’s contents.

This is an item component, so you set it with the bracket syntax from Chapter 21. Here’s a /give that hands you a sulfur cube item carrying a diamond inside it (typed in chat, so it keeps its leading /):

/give @s minecraft:sulfur_cube[minecraft:sulfur_cube_content={id:"minecraft:diamond",count:1}]

Heads-up on the item id. The component name sulfur_cube_content is exact, but a carrier item’s resource location is the kind of thing to confirm in-game rather than take on faith from a book. The line above uses the natural minecraft:sulfur_cube; if your game rejects it, turn on advanced tooltips (F3+H) and read the real id straight off a sulfur cube in your inventory.

A Real Banner Pattern Definition

A few of these registries (besides the sulfur cube) have fully documented fields, and banner_pattern is the simplest, so let’s build one to prove the pattern.

Banner pattern definition (banner_pattern). A banner pattern is a shape that can be added to a banner, defined by files in the banner_pattern folder. The format has just two fields: asset_id (the resource location for the texture asset) and translation_key (the translation key used to display the banner’s tooltip).

mypack/data/mypack/banner_pattern/forge_mark.json

{
  "asset_id": "mypack:forge_mark",
  "translation_key": "block.minecraft.banner.forge_mark.mypack"
}

The asset_id points at a texture you’d supply on the resource-pack side (the kind of art file you learned to place in Chapter 29); the translation_key is the name that shows in the banner’s tooltip. That’s the entire file, and it’s a clean example of the rhythm you’ll use for any documented registry: read the field list off its page, write exactly those fields, nothing invented.

The Rest of the Map: What Each Folder Is For

The registry map listed more folders than any one project will use. You don’t need the full field list for every one to be productive. You need to know what each folder is for and how to recognize when a build calls for it. Here’s that tour. When you’re ready to author one of these files, open the matching page on the live wiki, or crack open a vanilla data pack and read a real example, and write exactly the fields you find there: the same read-the-fields-then-write-them move you used above.

  • enchantment_provider — a “selection of enchantments for specific uses” (for example, choosing which enchantment a particular tool or loot source applies).
  • jukebox_song — “jukebox song definitions.” Its partner is the item component jukebox_playable, which is fully documented: it points an item at a jukebox song definition to play when inserted into a jukebox, and adds the song’s artist and title to the tooltip. So you already know exactly how an item uses a song.
  • instrument — “instruments for goat horns.” Its partner component instrument is documented (it shows the instrument description in an item’s tooltip), and defining an instrument in this folder counts as experimental while doing it through the component does not.
  • painting_variant — “size and texture of paintings.” Its partner component painting/variant sets which painting an item shows, displaying the name, artist, and size in the tooltip.
  • trim_material / trim_pattern — “colors, ingredients, and name of materials for trimming” and “textures and name of patterns for trimming.” You already met armor trims from the item side: the trim component back in Chapter 24, and the smithing_trim recipe type in Chapter 15; these two folders are where the materials and patterns themselves are defined.
  • trial_spawner — “configuration of trial spawners” (the wave and reward setup behind trial chambers).
  • timeline — “a timeline which specifies events and attributes according to the time of day.”
  • world_clock — “clocks used to keep track of internal time.”

Notice the pattern: several registries come in pairs, a definition folder (the registry) plus an item component that points at it. jukebox_songjukebox_playable, painting_variantpainting/variant, instrumentinstrument, banner_pattern ↔ the banner’s pattern data, sulfur_cube_archetypesulfur_cube_content. When you meet a new registry, ask “what component points at it?” The component side is usually the better-documented half, and it tells you half the story for free before you ever open the definition file.

Practice

  1. A gentle floating cube. Make a second archetype, mypack/data/mypack/sulfur_cube_archetype/water_buddy.json, that floats (buoyant: true) and eats bread ("items": "minecraft:bread") but has no contact_damage and no explosion (just leave those two optional fields out entirely, since both are optional). Give it a gentle sound_settings using soft sound events. Reload and confirm it loads with no errors. This proves you understand which fields are required and which you can omit.

  2. Your own banner pattern. Write mypack/data/mypack/banner_pattern/your_mark.json with an asset_id of mypack:your_mark and a translation_key you choose. You don’t need the texture to exist yet for the file to load. You’re practicing the definition file, the same two-field shape every banner pattern uses.

  3. Read the map. Without looking back, list three registry folders that are marked experimental (*) and one that is not. Then, for one registry the chapter only points you toward (say trial_spawner), write one sentence saying what its folder is for (straight from the map) and one sentence saying where you’d go to find its actual fields. This is the skill the chapter is really teaching: knowing the difference between “I can build this now” and “I know what this is and where to learn it.”

What Can Go Wrong

The experimental-settings warning screen appears. As soon as you put a valid file in an experimental folder (like sulfur_cube_archetype/), the game flags your whole pack as using experimental settings and warns you when you open the world in singleplayer. This is expected, not an error, so click through it. Just remember the two consequences: the warning will keep appearing, and you won’t be able to upload that world to Realms.

/reload doesn’t pick up your archetype change. This is the big one. Experimental settings use dynamic registries, and any changes regarding these features cannot be loaded using the reload command: the world must be exited and reopened (singleplayer), or the server rebooted (multiplayer) for the changes to take effect. So if you edit bouncing_bomb.json and /reload seems to do nothing, that’s not a bug: fully exit the world and re-enter (or reboot the server). Compare that to recipes, loot tables, and tags from earlier chapters, which /reload can refresh live. Dynamic-registry folders are the exception.

A path typo silently makes the wrong ID. Remember the registry rule: the folder name is the registry name and the file path is the ID. If you save your archetype to data/mypack/sulfur_cube_archetypes/ (plural) or misspell the folder, the game won’t find a registry by that name and your file just won’t load as an archetype, often with no obvious complaint. Double- check the folder is spelled exactly as it appears in the map: sulfur_cube_archetype, singular. The same goes for banner_pattern, villager_trade, and the rest: copy the names from the map, don’t trust your memory.

Chapter 39 — Dialogs

What You’ll Build

Every screen you’ve shown a player so far has been text. Chapter 5 taught you to print rich, clickable messages with /tellraw; Chapter 11’s scoreboards and Chapter 5’s /title put words on the screen. But text scrolls past, and a clickable link in chat is easy to miss. What if you could pop up a real window: a box in the middle of the screen, with a title, a message, and a row of buttons the player has to click before they can keep playing?

That window is called a dialog, and you can build one from a single JSON file in your data pack.

By the end of this chapter you’ll have a quest-giver dialog in the mypack pack you started in Chapter 9: a pop-up that greets the player and offers several buttons (accept a quest, ask for a reward, or close), each running a different command. Along the way you’ll meet the five kinds of dialog, the four kinds of input field a dialog can collect from the player, and the /dialog command that shows and clears them. We’ll test everything in the world you’ve used since Chapter 1.

Figure (to be captured). the finished quest_giver dialog open in-game, showing a title, a message, and three option buttons

What a dialog is

Here’s the one-line definition:

Dialogs are simple modal windows that can display information and receive player input.”

Two new words there. A window is a box drawn on top of the game. Modal means it takes over: while a dialog is open, player controls are disabled until the player leaves it by clicking a button, pressing the Escape key, or clicking the warning button next to the title. So a dialog is a screen the player must deal with before returning to the game, not a message you just glance at.

What can a dialog do? Here are the kinds of interaction it supports:

“Sending messages or information using text components, including rich text formatting and clickable links… Receiving player input through input control fields such as textbox, toggle, slider, and option selection; Executing commands via action buttons… and Navigating between multiple dialogs using nested structures.”

So a dialog can show text (using the text components you learned in Chapter 5), collect input (typed text, a checkbox, a slider, a dropdown), and do things when the player clicks a button (run commands, or open another dialog). A dialog has up to three parts: a Header (the title), some Body elements (the message and any input fields, scrollable if there are a lot), and an optional footer (the confirmation buttons).

Where dialogs live

A dialog is a .json file, and like every other piece of a data pack it goes in a specific folder. The dialogs are defined in data packs inside the dialog directory. Following the same data/<namespace>/<registry>/... pattern you’ve used since Chapter 9, a dialog called hello in your mypack namespace lives at:

data/mypack/dialog/hello.json

and its namespaced ID (the name you’ll use to show it) is mypack:hello. The /dialog command’s own example spells this rule out: a dialog file at data/custom/dialog/example/test.json has the ID custom:example/test. Just like functions and recipes, sub-folders become part of the ID after a slash.

Modern Minecraft — the “experimental settings” footnote

If you read about dialogs online you may see them called “experimental.” Here’s the precise truth, so you’re not confused. In the data pack folder list, the dialog folder is marked with a red asterisk, and here’s what that asterisk means:

“If a folder is marked with an asterisk… it means that the game considers the feature to be experimental, and having a valid file inside any of these folders will mark the data pack as using experimental settings.”

So putting any dialog file in data/<ns>/dialog/ flags your whole pack as “using experimental settings.” Here’s what that flag does: opening such a world in singleplayer “will display a warning screen,” and worlds using experimental settings “cannot be played on Realms.” There’s also a practical catch from the same page: changes to these folders “cannot be loaded using the reload command: the world must be exited and reopened.” So after editing a dialog file, leave the world and come back, don’t just /reload.

But notice what the flag does not mean: the dialog feature is fully documented, has its own /dialog command, and works. It is the folder that carries the experimental marker, not a half-built feature. Dialogs are a real, shipped tool. Just expect the warning screen and the no-Realms rule.

Your first dialog: a notice

The simplest dialog is a notice: a pop-up with a message and a single “Ok” button. Let’s build one.

data/mypack/dialog/hello.json

{
  "type": "minecraft:notice",
  "title": "Welcome to mypack!",
  "body": {
    "type": "minecraft:plain_message",
    "contents": "This is your very first dialog window."
  }
}

Three fields, each from the dialog format. type says which kind of dialog this is: minecraft:notice. title is the text shown at the top; it’s required, and it’s a text component, so a plain string like "Welcome to mypack!" is fine (a string is the simplest text component, as you learned in Chapter 5). body holds the message: here a single plain_message body element, whose contents is the line we want to display. We’ll cover body elements properly in a moment; for now, that’s a complete, working dialog.

Because dialogs sit in an experimental-settings folder, save the file, then exit your world and reopen it (don’t rely on /reload).

Showing and clearing it

To put a dialog on a player’s screen you use the /dialog command. It has two forms:

“/dialog show <targets> <dialog> — Shows a dialog screen… to specified players. /dialog clear <targets> — Clears currently displayed dialogs for specified players.”

show needs two things: who sees it (a target selector like @p or @a) and which dialog. The “which” can be the namespaced ID of a dialog file. So to show yourself the hello dialog, run this in a function (the rule since Chapter 9: commands live in .mcfunction files, no leading slash):

data/mypack/function/show_hello.mcfunction

dialog show @s mypack:hello

Run function mypack:show_hello and the window pops up. To take it away again, say, from everyone at once, use clear:

data/mypack/function/clear_dialogs.mcfunction

dialog clear @a

Under the Hood — inline dialogs (skippable)

The <dialog> argument doesn’t have to be a file ID. It can also be an inline SNBT defining the dialog structure directly in the command. For example, /dialog show @p {type:"minecraft:notice",title:"Hello"} writes the whole dialog right there in the command. That’s handy for a quick throwaway pop-up, but for anything you’ll reuse, a file is far easier to read and edit. We’ll always use files in this book.

The five dialog types

Every dialog’s type field picks one of five shapes. They all live under the minecraft:dialog_type registry; here’s what each is for:

typeWhat it looks like
minecraft:noticeA single action button in the footer. Good for “press Ok to continue” messages.
minecraft:confirmationTwo buttons, a yes and a no. “Two action buttons in footer.” Good for “Are you sure?” questions.
minecraft:multi_actionA scrollable list of as many buttons as you want, “arranged in columns.” This is the quest-giver shape.
minecraft:server_linksA built-in list of the server’s links. You rarely build this yourself.
minecraft:dialog_listA list of buttons that each open another dialog: a menu of menus.

Every type shares the common fields from the top of the dialog format: the required type and title, an optional body, optional inputs, and a few switches. Two of those switches are worth knowing now. Here’s pause:

pause: If the dialog screen should pause the game in single-player mode. Defaults to true.”

and after_action, which decides what happens after the player clicks a button. It “Defaults to close,” meaning the dialog closes and hands the player back to the game. You can leave both at their defaults for everything in this chapter.

Body elements: the message inside

The body field holds what’s shown between the title and the buttons. Each piece is called a body element, and there are two kinds.

The first is plain_message, “A multiline label,” just text:

plain_messagecontents: Text component.”

You already used one in hello.json. The second is item: it shows an actual item, the way it looks in your inventory, with an optional description beside it:

item … An item with optional description. It appears like it is in the inventory slot when the mouse hovers over the item.”

Here’s a dialog body showing both an item and a message:

data/mypack/dialog/reward_preview.json

{
  "type": "minecraft:notice",
  "title": "Your reward",
  "body": [
    {
      "type": "minecraft:item",
      "item": {
        "id": "minecraft:diamond",
        "count": 3
      }
    },
    {
      "type": "minecraft:plain_message",
      "contents": "Finish the quest to earn these."
    }
  ]
}

Notice body is now a list (square brackets) holding two elements. This is allowed: body can be a list of body elements or a single body element. When you have one element you can write it bare (as in hello.json); when you have several, you put them in a list. The item element’s item field is an item stack (an id and a count), exactly the shape you’ve seen since Chapter 15.

Heads up — what dialog text can’t do. A plain_message does not support nbt, score, and selector components. Those three text-component types from Chapters 5–12 (the ones that pull live data from the world) won’t resolve inside a dialog. Stick to plain text, colors, and styles in dialog bodies.

Buttons, and the type key that trips everyone up

A dialog’s buttons are where the action happens, literally. Each button is a small compound with a label (the text on the button, a text component) and, optionally, an action field telling the game what to do when it’s clicked.

Here is the single most important detail in this chapter, and it’s a place where dialogs differ from everything you learned in Chapter 5. Back in Chapter 5, a clickable chat message used a click_event whose kind was named by an action field ("action": "run_command"). Inside a dialog file the rule flips. The rule is explicit:

“Static actions… are identical to text component events… They use the same format but with the action tag replaced with type.”

Read that twice. The kinds of action are the same ones from Chapter 5 (run_command, suggest_command, open_url, show_dialog, and so on), but the field that names the kind is called type here, not action. An example button makes it concrete:

{
  "label": "Show dialog label",
  "action": {
    "type": "show_dialog",
    "dialog": "custom:my_dialog"
  }
}

Look carefully: the button has a field literally named action (the action to perform), and inside that, the kind of action is given by type, not by another action. So a button that runs a command looks like this:

{
  "label": "Give me a diamond",
  "action": {
    "type": "run_command",
    "command": "give @s diamond"
  }
}

This actiontype shape is exactly what the /dialog confirmation example uses, too. If you write "action": "run_command" (the Chapter 5 way) inside a dialog, the button won’t work. Remember: inside a dialog, the action’s kind is type.

Where buttons go in each type

Each dialog type names its button field differently. Here’s each one:

  • notice has a single action compound (one footer button). If you leave it out, you get a default button with a gui.ok label and no action, a plain “Ok” that just closes.
  • confirmation has a required yes and a required no, each a button compound.
  • multi_action has a required actions (“Non-empty list of click actions”), plus an optional exit_action for the footer/Escape button.
  • dialog_list and server_links likewise use exit_action for leaving.

A button compound in any of these slots takes the same fields: label (required), an optional tooltip text shown on hover, an optional width, and the action compound we just dissected.

Input controls: asking the player for something

So far our dialogs only tell. To ask, a dialog adds an inputs list of input controls: the text boxes, checkboxes, sliders, and dropdowns mentioned earlier. There are four kinds, from the minecraft:input_control_type registry:

Control typeWhat the player sees
minecraft:text“A basic, single line, text input.” A box to type in.
minecraft:boolean“A checkbox.” On or off.
minecraft:single_option“A preset option selection.” A dropdown of choices you define.
minecraft:number_range“A number slider.” Drag between a start and an end.

Every input control shares two required fields:

key: String identifier of value used when submitting data, must be a valid template argument (letters, digits and _). label: A text component to be displayed to the left of the input.”

The key is the name you’ll use to read back what the player entered. Think of it as a labelled box that catches their answer. Notice the exact wording: the key “must be a valid template argument (letters, digits and _).” That should ring a bell from Chapter 25: those are precisely the rules for a macro key. That’s not a coincidence, and it’s the bridge to the next section.

Here’s a text input control:

{
  "type": "minecraft:text",
  "key": "name",
  "label": "Your hero name:"
}

The other three are similar. A single_option carries an options list, each option a compound with an id (the value sent when chosen) and a display (the text shown). A number_range carries a required start and end (its minimum and maximum), and an optional step. A boolean carries an optional initial (whether it starts checked).

Dynamic actions: turning input into commands

Now the payoff for input controls, and the place where the function macros from Chapter 25 finally earn their keep. A static action runs a fixed command: give @s diamond is the same every time. A dynamic action builds its command from what the player typed or chose, using the macro templates you learned in Chapter 25.

Here’s the main one, dynamic/run_command:

“This action will build a run_command event using a provided macro template (example: /say $(message) if you have a text input with an ID message)… template: A string with a macro template to be interpreted as a command.”

So instead of a fixed command, a dynamic action has a template: a command with $(key) placeholders, exactly the $(key) syntax from Chapter 25. When the button is clicked, the game fills each $(key) with the matching input control’s value (matched by the key field you set), then runs the finished command. The same Chapter 25 rule applies: every $(key) in the template must have a matching input key, or nothing runs.

Let’s put a text input and a dynamic action together. This dialog asks for a name, then announces it:

data/mypack/dialog/name_sign.json

{
  "type": "minecraft:notice",
  "title": "Sign your name",
  "body": {
    "type": "minecraft:plain_message",
    "contents": "Type a hero name and press Announce."
  },
  "inputs": [
    {
      "type": "minecraft:text",
      "key": "name",
      "label": "Your hero name:"
    }
  ],
  "action": {
    "label": "Announce",
    "action": {
      "type": "dynamic/run_command",
      "template": "say A new hero rises: $(name)"
    }
  }
}

Trace the connection: the text input’s key is name, and the template says $(name). Type “Steve,” press Announce, and the game runs say A new hero rises: Steve. Change the input’s key and the template’s $(name) together, or it breaks: same discipline as any macro.

Under the Hood — dynamic/custom (skippable). There’s a second dynamic action, dynamic/custom, which builds a minecraft:custom event using all input values and bundles every input into a compound sent to the server. That’s for server mods and plugins that listen for custom network messages, well beyond a data pack. We won’t use it, but now you know the word if you meet it.

Dialog tags: the pause menu and the quick-actions key

You don’t always want to /dialog show a window by hand. A data pack can attach a dialog to two built-in spots in the game, using two dialog tags (tags being the “groups of things” you learned in Chapter 14, here grouping dialogs). The two tags are:

  • pause_screen_additions — “Dialogs in this tag replaces the ‘Report Bugs’ button or the ‘Server Links’ button on the pause screen.” Put a dialog here and players can open it any time from the Escape menu. If the tag has a single element, the button leads directly to that single dialog; with several, the button opens a built-in menu listing them all.
  • quick_actions — “Dialogs to open when pressing quick actions” (a keybind). One element opens that dialog directly; several open a chooser.

A dialog tag is a tag file in the minecraft namespace (because you’re adding to Minecraft’s built-in tag), shaped like every tag file since Chapter 14: a values list. To put your quest-giver on the pause menu:

data/minecraft/tags/dialog/pause_screen_additions.json

{
  "values": [
    "mypack:quest_giver"
  ]
}

Dialogs vs. tellraw and title

You now have three ways to talk to a player: /tellraw (Chapter 5), /title (Chapter 5), and dialogs. When do you reach for each?

  • /tellraw writes a line to chat. Use it for log-style feedback, hints, and clickable links the player can ignore. It doesn’t interrupt play.
  • /title flashes big text over the screen, then fades. Use it for moments like “Level Complete!” or a countdown. It can’t take input, and it can’t be clicked.
  • A dialog is a window the player must answer. Use it when you need a choice or input: a menu, a confirmation, a name to type, a difficulty to pick. It’s the only one of the three that pauses the game and collects answers.

A rough rule: if you’re informing, use tellraw or title; if you’re asking, use a dialog.

Walkthrough: the quest-giver dialog

Time to build the chapter’s project: a multi_action dialog that greets the player and offers several choices. We’ll show an item, write a welcome line, and give three buttons: accept the quest, peek at the reward, and leave.

data/mypack/dialog/quest_giver.json

{
  "type": "minecraft:multi_action",
  "title": "The Village Elder",
  "body": [
    {
      "type": "minecraft:item",
      "item": {
        "id": "minecraft:emerald",
        "count": 1
      }
    },
    {
      "type": "minecraft:plain_message",
      "contents": "Greetings, traveler. Our village needs a hero. Will you help?"
    }
  ],
  "columns": 1,
  "actions": [
    {
      "label": "Accept the quest",
      "action": {
        "type": "run_command",
        "command": "say I accept the quest!"
      }
    },
    {
      "label": "What's the reward?",
      "action": {
        "type": "show_dialog",
        "dialog": "mypack:reward_preview"
      }
    },
    {
      "label": "Give me a starter blade",
      "action": {
        "type": "run_command",
        "command": "give @s iron_sword"
      }
    }
  ],
  "exit_action": {
    "label": "Maybe later",
    "action": {
      "type": "run_command",
      "command": "say Farewell, traveler."
    }
  }
}

Read it top to bottom. The type is minecraft:multi_action, so the buttons live in the actions list. The body shows an emerald and a greeting. columns set to 1 stacks the buttons in a single column (it defaults to 2). Each button has a label and an action compound whose kind is named by type, and note the three different kinds: two run_command buttons and one show_dialog button that opens the reward_preview dialog from earlier in the chapter (dialogs opening dialogs, the “nested structures” mentioned earlier). The exit_action is the footer/Escape button, “Maybe later.”

Now a function to summon the elder:

data/mypack/function/show_quest.mcfunction

dialog show @s mypack:quest_giver

Save everything, exit and reopen your world, then run function mypack:show_quest. The Village Elder appears, emerald and all. Click “What’s the reward?” to jump to the reward window; click “Give me a starter blade” to actually receive an iron sword; click “Maybe later” to bow out.

Figure (to be captured). the quest_giver dialog open, three option buttons stacked in one column, an emerald shown above the greeting

Practice

  1. Add a difficulty picker. Give the quest-giver an inputs list with a single_option control (key difficulty) offering three options: easy, normal, hard. Add a fourth button whose action is dynamic/run_command with template say I chose $(difficulty) mode. Show it, pick a difficulty, click the button, and watch the right message print.

  2. A confirmation dialog. Build data/mypack/dialog/confirm_reset.json of type minecraft:confirmation with a title of “Reset your progress?”, a yes button that runs a command of your choice, and a no button (label “Cancel”) with no action, so it just closes. Show it with a new function and try both buttons.

  3. Hook it to the pause menu. If you didn’t already, add the data/minecraft/tags/dialog/pause_screen_additions.json tag pointing at mypack:quest_giver. Reopen the world, press Escape, and open the elder from the pause screen, no command needed.

What Can Go Wrong

  • You used "action" instead of "type" for the button’s kind. This is the dialog mistake. Inside a dialog file, the button has an action compound, and the kind of action inside it is named by type ("type": "run_command"), not by another action. If a button does nothing when clicked, check this first.

  • You edited the file and ran /reload, but nothing changed. Dialogs live in an experimental-settings folder, and those cannot be loaded using the reload command: the world must be exited and reopened. Leave the world and come back after every dialog edit.

  • A dynamic action’s $(key) doesn’t match an input’s key. Just like macros in Chapter 25, every $(key) in a template must have a matching input control key, or the command won’t run. If your template says $(name), make sure an input has "key": "name", spelled identically.

Chapter 40 — Structure Blocks and Structure Files

Part XI — Advanced: World Generation. This is the start of the hardest part of the book. World generation is where data packs become most powerful and most complex, and these chapters assume you are comfortable with everything from Parts I–VIII. Take them slowly. The good news: this first chapter doesn’t involve a single line of JSON. It’s about a special block and the files it writes.

What You’ll Build

Everything you’ve built so far, you’ve built by describing it: a recipe is a JSON file that lists ingredients, a loot table is a JSON file that lists drops. But some things are easier to build by hand: a cottage, a watchtower, a fountain. You place the blocks in the world the normal way, then you want to keep that building so your data pack can stamp copies of it wherever you like.

That’s exactly what a structure block does. By the end of this chapter you’ll take a small building you place by hand in your test world (the one from Chapter 1), capture it with a structure block into a structure file (a .nbt file the game writes for you), and then move that file into the mypack data pack you started in Chapter 9. Once it lives in your pack, you can drop the building back into the world any time, in any of your functions.

This is also the foundation for the next chapter. In Chapter 41 you’ll learn how to make Minecraft generate a building naturally as the world is explored, and every piece of those generated structures is one of these same .nbt files. Save a building first; teach the world to spawn it second.

Figure (to be captured). a small cottage in the world with a structure block beside it, the white structure outline drawn around the building

What a structure block is

Here’s the plain definition:

“A structure block is used to generate structures manually. They can also be used to save and load structures, alongside structure void blocks.”

So a structure block has two jobs: save a chunk of the world (its blocks, and optionally the entities standing in it) into a file, and load that file back into the world later. Think of it as a camera for buildings: it photographs a box-shaped region and can re-print the photo anywhere.

You can’t find a structure block while mining; it’s an operator tool. Structure blocks are obtained using the setblock, fill, or give commands, or from the Creative inventory in Java Edition through the “Operator Utilities” tab. Once placed, structure blocks are unbreakable in Survival and have the same blast resistance as bedrock, so don’t worry about a creeper destroying your work. To give yourself one, you’ll run a command from a function (commands live in .mcfunction files, never the chat bar; that’s been our rule since Chapter 1). Here’s a tiny helper for your pack:

mypack/data/mypack/function/get_structure_block.mcfunction

give @s minecraft:structure_block

Run it with function mypack:get_structure_block, and a structure block appears in your inventory.

To use the block, place it and right-click it to open its window. There’s a catch worth knowing: the GUI opens only if the player is in Creative mode and has permission level 2 or higher. A GUI (graphical user interface) is just the pop-up screen of buttons and text boxes the block shows you. So: be in Creative mode and be an operator. (You made yourself an operator back in Chapter 8.)

One structure block can be switched between several modes, and switching between modes preserves the settings of the structure block wherever possible, so you won’t lose your typed-in name when you flip from one mode to another. Once you name a structure, its name appears above the structure block when highlighted, preceded by the block mode (e.g. “Save:minecraft:example”), a handy way to see at a glance what a block is set to do.

The modes that matter for us are Save, Load, and Corner. There are two more (a Windows-only 3D Export mode and a deprecated Data mode), and we’ll mention what those are near the end so you recognize them, but you won’t need them.

The structure file: what .nbt is and where it lives

When a structure block saves a building, it writes a structure file:

“A structure file (also called structure template) is an NBT file that stores small structures of blocks and entities. Structure files are used to store some structures such as end cities, igloos, and fossils.”

So a structure file is a single file, ending in .nbt, that holds a snapshot of some blocks (and maybe entities). It’s the same kind of file Mojang themselves use for the prefab pieces of vanilla structures. NBT stands for Named Binary Tag; for now all you need to know is that it’s a packed binary format you don’t open in a text editor. (We’ll peek at what’s inside it later in this chapter, under “Under the Hood.”)

The important fact is where these files go inside a data pack. You learned the data-pack folder rule in Chapter 9: a file at data/<namespace>/<registry>/<path> loads into Minecraft under the ID <namespace>:<path>. Structure files follow that exact rule, with one twist: they use the .nbt extension instead of .json or .mcfunction. The structure folder holds .nbt files defining a saved structure of blocks, and structure files always use the .nbt extension.

So a structure called cottage in your mypack namespace will live at:

mypack/data/mypack/structure/cottage.nbt

and its namespaced ID (the name you’ll use to place it) is mypack:cottage. Same pattern as functions, recipes, and everything else: the folder is named for what’s inside it, and sub-folders become part of the ID after a slash.

Modern Minecraft — structure, not structures

If you follow an older tutorial online, you may see the folder called structures (plural) sitting next to a data folder shaped a bit differently. Modern data packs use the singular structure folder under data/<namespace>/. The world-save folder the game writes saved files into is still called generated/<namespace>/structures (plural; you’ll see that path in a moment), but the folder inside your pack is the singular structure. Watch the spelling; a misnamed folder is the single most common reason a structure won’t load.

Walkthrough A — Save a building

Let’s capture a building. First, build something small by hand in your test world (a little cottage, say, no bigger than a dozen blocks on a side). Keep it modest: the maximum structure size is 48×48×48 in Java Edition, so a region can be at most 48 blocks in each direction. Note the north-west bottom corner of your building, the corner toward the smallest X, Y, and Z, the same corner-reasoning you used for /fill and /clone in Chapter 2.

Now place a structure block one block away from that bottom north-west corner, open it, and make sure it’s in Save mode. Save mode lets you highlight a structure in the world and save it to memory, the level file, or a separate file. The window gives you these fields:

  • Structure Name — a text box for entering the resource location of the structure to be saved. Type mypack:cottage. Here’s the namespace rule: if no namespace is specified, a default value of minecraft is used; you change that by prefixing the structure name with <namespace>:. So always type your mypack: prefix. Otherwise the game files it under minecraft and you’ll hunt for it in the wrong place.

  • Relative Position / Offset — the X, Y, and Z offsets of the structure, relative to the bottom north-west corner of the structure block. Sets the origin of the structure outline. This is where the captured box starts, measured from the block. The origin must be at most 48 blocks away in all directions.

  • Structure Size / Size — X, Y, and Z offset from the Relative Position coordinates. This sets the opposite corner of the structure and defines the structure’s size. In other words, Offset picks one corner and Size sets how far the box extends. When you get it right, it generates an outline surrounding the structure that’s mostly white except for the red, green, and blue lines that represent the X, Y, and Z axis. That glowing box is your structure outline (also called the bounding box); adjust the numbers until it hugs your building exactly.

  • Include entities — when saving the structure, if this option is on, it saves any entities within the structure as well. Turn this on if you want, say, an armor stand or a painting captured along with the blocks; leave it off for a plain building.

When the white outline wraps your cottage perfectly, press Save. In Java Edition this saves the structure to a file, and the name of the structure is the name of the file. Your building is now a .nbt file. (Two cautions worth knowing: structures can be saved to a file only by manually pressing this button. If a structure block in Save mode is instead powered by redstone, the structure is only saved in memory, and reloading the world clears any structures stored in memory. So press the button yourself; don’t wire a structure block to redstone and expect a file.)

Figure (to be captured). the structure block Save-mode GUI, with Structure Name “mypack:cottage” typed in and the size fields filled

Corner mode — let the game measure the box for you

Typing exact Offset and Size numbers is fiddly. Corner mode does the measuring for you: it allows for an easier and automatic size calculation while saving or loading structures. The idea is to mark the opposite corner of your building with a second block.

Here’s the recipe: place a corner block on the opposite corner of a save structure block or a second corner structure block. Then, using a save block, press “DETECT”. So:

  1. Place a structure block at the bottom north-west corner of your build, set it to Save mode, and name it mypack:cottage.
  2. Place a second structure block at the opposite (top south-east) corner, set it to Corner mode, and give it the same name, mypack:cottage.
  3. Back in the Save block, press Detect structure size and position (the “DETECT” button).

That button automatically calculates the size and position of the structure using a corner block placed on the opposite corner of the structure. One rule to stress: the name of the structure in the save block must match the name within the corner block, or the size calculation fails. Same name on both blocks, every time. When it works, the white outline snaps around your whole building and you can press Save.

Where the file landed — and the move into your pack

Here’s the part that surprises people. When you press Save in Java Edition, the file does not go into your data pack. Here’s exactly where it goes:

“Structures are saved in .minecraft/saves/(WorldName)/generated/(namespace)/structures.”

So your cottage is now sitting at:

.minecraft/saves/<your world>/generated/mypack/structures/cottage.nbt

(<your world> is the folder for the world you’re testing in; mypack is the namespace you typed because you prefixed the name.) This generated/ folder is a scratch area the game writes structure-block saves into. It is not part of any data pack, and it only exists for the one world.

To turn your one-off save into a real, shippable part of your pack, you copy the .nbt file into your pack’s structure folder. Close Minecraft (or at least the world), open your file explorer, and move the file:

FROM:  .minecraft/saves/<your world>/generated/mypack/structures/cottage.nbt
TO:    mypack/data/mypack/structure/cottage.nbt

That single copy is the whole “export” step: build, save, copy into the pack. This is the round trip on the structure-file side: files can be saved and loaded using the structure block, and when saved from structure blocks they are written into the generated/<namespace>/structures subfolder in the world save folder; inside a data pack, structure files are stored as .nbt files in the structure folder. Once the file is in mypack/data/mypack/structure/cottage.nbt, anyone who installs your data pack gets your cottage; it travels with the pack.

What Went Wrong? — “I can’t find the file”

Two things trip people up. First, the file is under generated/, not under datapacks/; those are different folders inside the world save. Second, if you forgot the mypack: prefix when naming the structure, the game used the default minecraft namespace, so your file is at generated/minecraft/structures/cottage.nbt instead. Either way, look for the .nbt file by its name; then copy it into mypack/data/mypack/structure/.

Walkthrough B — Load the building back

Now that cottage.nbt lives in your pack, let’s stamp a copy into the world. There are two ways: a structure block in Load mode, and the /place template command.

A structure block in Load mode lets you load and rotate saved structures. Place a structure block where you want the bottom north-west corner of the copy to appear, set it to Load mode, and type the name mypack:cottage. The useful options here:

  • Relative Position / Offset — same idea as before: where the loaded copy sits relative to the block.
  • Rotation (0, 90, 180, 270) — sets the rotation of the structure to 0° (no rotation), 90° clockwise, 180° clockwise, and 270°. Great for facing a building a different way.
  • Mirror — flips the structure left-to-right or front-to-back.
  • Include entities — includes any entities saved in the structure file when loading the structure. Off by default. Turn it on if you saved entities and want them back.
  • Structure Integrity and Seed — removes random blocks that compose the structure based on a user-defined seed. Lower integrity values result in more blocks being removed; the integrity value must be between 0.0 and 1.0. Leave this at 1.0 for a perfect copy; lower it later if you want a ruined, decayed look.

Load mode looks for the file in a set order, and that order matters. It’s why copying the file into your pack works. It searches, in order: from memory; from a file (…/generated/<namespace>/structures/); from data packs; then built-in structures from minecraft.jar. So a structure block will find mypack:cottage inside your data pack (third in the list) even after the generated/ scratch copy is gone.

Press Load. Note the two-press behavior in Java Edition: press this button once to prepare the outline preview of the structure, then, when satisfied with the position, press again to generate the structure. First press previews where it’ll land; second press builds it.

Try It! — place a structure from a function

A structure block is great for testing, but the whole point of putting the file in your pack is that your functions can place it. Structure files can also be placed using the place template command. So this one-line function stamps your cottage at the spot the command runs from:

mypack/data/mypack/function/place_cottage.mcfunction

place template mypack:cottage ~ ~ ~

Run it with function mypack:place_cottage while standing where you want the building’s corner, and the cottage appears, no structure block needed. This is exactly how a data pack ships a build.

Under the Hood — what’s inside a .nbt structure (skippable)

You never hand-edit a structure file, but it helps to know it isn’t magic. Here’s the shape of the NBT inside. The root holds a DataVersion (which Minecraft version made it), a size (three numbers: the box’s length, height, and depth), a palette (the list of distinct block types used, each with its block ID and block-state properties), a blocks list (every individual block, stored as a position plus an index into the palette, so a wall of 100 stone bricks references one palette entry 100 times instead of repeating “stone brick” 100 times), and an entities list (any saved entities, each with a position and its entity data). That palette-and-index trick is why a structure file stays small even for a big build.

You do not write this by hand. It’s packed binary, the structure block fills it in for you, and the only sensible way to create or change one is the build-save-copy workflow in this chapter. The exact byte-level binary layout of the .nbt file (a gzip-compressed Named Binary Tag stream) is a topic for tooling authors, not pack builders. By design, this book teaches the structure-block workflow, and you never need to crack the file open.

Modern Minecraft — Data mode and structure voids

Two pieces of structure-block lore you’ll see mentioned elsewhere but won’t use here. Data mode is a deprecated mode, superseded by the jigsaw block but still used in some vanilla structures. It marks spots inside vanilla structures to run hardcoded behavior (placing the treasure chest in a shipwreck, for example) and can be used only during natural generation, so it’s not something your pack drives directly. We’ll meet its modern replacement, the jigsaw block, in Chapter 41. The other term is the structure void block, mentioned in the structure-block definition: it’s a special block that marks “leave whatever’s already here” inside a saved structure, so a captured piece can have see-through gaps. You won’t need either to ship a building.

Practice

  1. Save a build of your own. Build something you actually want to reuse (a guard tower, a market stall, a decorative well), keeping it under 48×48×48. Capture it with Save mode (use Corner mode to measure it), name it with your mypack: prefix, and copy the resulting .nbt out of generated/mypack/structures/ into mypack/data/mypack/structure/. Confirm it loaded by running a place template mypack:<name> ~ ~ ~ function.

  2. A rotated row. Write a function that places your structure three times in a row, each copy facing a different way, by running place template at three offset positions. (You can use the ~ relative coordinates from Chapter 2 to space them out.)

  3. A ruined version. Load your structure with a structure block, but lower the Structure Integrity below 1.0 (try 0.6) and watch random blocks drop out. This is how you’d make a weathered, half-collapsed ruin from the same file you used for the pristine building.

What Can Go Wrong

  • The structure won’t load from the pack. Almost always a folder-name or namespace slip. Inside the pack the folder is structure (singular), and the full path must be mypack/data/mypack/structure/cottage.nbt. Check the spelling, check the .nbt extension is really there (not cottage.nbt.txt), and check the name you load matches the name you saved, including the mypack: namespace.

  • “Detect” doesn’t size the box. The Save block and the Corner block must have the exact same name. The rule is firm: the name of the structure in the save block must match the name within the corner block, or the size calculation fails. Re-type the name on both blocks and press Detect again.

  • I saved it but there’s no file. You either powered the structure block with redstone (which only saves to memory, never to disk) or never pressed the Save button by hand. Open the Save block and press the button yourself; then look in generated/<namespace>/structures/.

Chapter 41 — Structures and Structure Sets

What You’ll Build

In Chapter 40 you saved a small building to a .nbt structure file and learned to drop it into the world by hand with a structure block or /place. That building just sits in a folder until you tell the game to place it. In this chapter you make it appear on its own, scattered through freshly generated land, the same way villages and pillager outposts do.

To get there you’ll write four small JSON files in your mypack data pack:

  • a structure definition — says what the structure is and which biomes it may grow in;
  • a template pool — lists the saved piece (or pieces) the game may choose from;
  • a processor list — an optional step that ages or alters blocks as they place;
  • a structure set — decides where in the world copies show up, and how far apart.

By the end you’ll have a tiny outpost that generates in plains biomes, and you’ll have closed the loop from Chapter 34: the “forge cache” chest we promised back then can finally live inside a real, self-generating structure, with its chest pointed at the loot table you already wrote.

Figure (to be captured). a small outpost building standing in a plains biome, freshly generated, no command used

Heads up — this is the hard part of the book. World generation has more moving parts than anything you’ve built so far, and four files all have to agree with each other. We’ll go one file at a time, and the outpost we build uses a single saved piece so you can see the whole machine without drowning in it. Take it slowly; re-read a section if a field doesn’t click yet.

Concepts

What a structure actually is

A structure (the wiki also calls it a “generated structure” or “structure feature”) is a naturally-generated formation you can find with /locate and place by hand with /place. Villages, pillager outposts, ancient cities, ocean monuments: all structures. They will not appear if a world was created with the “Generate Structures” option turned off.

One detail matters for understanding when your structure shows up: structures are generated for a chunk after the terrain of that chunk has been formed. The ground is shaped first; the structure is dropped onto it afterward. That ordering is why a structure can ask the game to flatten or bury the terrain under it: the land is already there to adjust.

Four registries, one structure

Here’s the mental model. Four separate files, each in its own folder under data/mypack/worldgen/, work together:

FileFolderJob
Structure definitionworldgen/structure/What the structure is; which biomes; spawn rules
Template poolworldgen/template_pool/Which saved piece(s) to place
Processor listworldgen/processor_list/Block-by-block changes as it places (optional)
Structure setworldgen/structure_set/Where in the world, and how often

The structure definition points at a template pool. The template pool points at your .nbt file (and, optionally, at a processor list). The structure set points at the structure definition. Nothing points at the structure set, and that’s the surprising part. As the wiki puts it: a structure set is “not referenced in a dimension or biome. Instead, the existence of the resource is enough to make the structures generate.” Drop the file in, and the structure starts appearing.

Modern Minecraft. All four of these are dynamic registries, the same family as biomes, dimensions, and enchantments. Dynamic registries are read when the world loads. That means /reload does not update them. When you change a worldgen file you must exit the world and open it again. (And changes only affect newly generated chunks: land that already exists keeps whatever generated there the first time.) Keep this in your back pocket; it’s the number-one source of “why isn’t my change doing anything?” in this chapter.

Walkthrough

We’ll assume you finished Chapter 40 and have a saved building .nbt (there we saved cottage.nbt). This chapter uses one called outpost as its running example:

data/mypack/structure/outpost.nbt

If you only have your cottage.nbt (or any other small saved .nbt), that’s fine; just use its name in place of outpost below. We’ll build the four JSON files from the inside out: the pool first (it names your .nbt), then the processors, then the structure definition, then the structure set.

Step 1 — The template pool

A template pool is a group of structure pieces that the jigsaw system may choose from. A “piece” is usually one saved structure template; during generation the game randomly picks pieces from the pool. Pools are stored as JSON files in data/<namespace>/worldgen/template_pool.

Our outpost is a single building, so our pool has exactly one piece. Here’s the file:

data/mypack/worldgen/template_pool/outpost.json

{
  "fallback": "minecraft:empty",
  "elements": [
    {
      "weight": 1,
      "element": {
        "element_type": "minecraft:single_pool_element",
        "projection": "rigid",
        "location": "mypack:outpost",
        "processors": "minecraft:empty"
      }
    }
  ]
}

Field by field, straight from the Template pool format:

  • fallback — another template pool, used “for terminating pieces (such as the end of a village road) or as fallback if structures in this pool can’t generate.” We have nothing to fall back to, so we point it at the built-in empty pool, minecraft:empty.
  • elements — the list of pieces to randomly select from. Ours has one entry.
  • weight — “how likely this element is to be chosen when using this pool. Value between 1 and 150 (inclusive).” With one element the weight doesn’t compete with anything, so 1 is fine.
  • element — the piece itself:
    • element_type — we use minecraft:single_pool_element, which “places a single structure template.” (There are four others; see the box below.)
    • projectionrigid “to place a fixed structure (like a house),” or terrain_matching “to match the terrain height (like a village road).” A building should stay rigid; a flat path that follows hills would use terrain_matching. We want a solid building, so rigid.
    • location — the structure template to place. This is the namespaced ID of your Chapter 40 .nbt file: mypack:outpost points at data/mypack/structure/outpost.nbt.
    • processors — the processor list to run on the template. We have none yet, so minecraft:empty. We’ll come back and swap this in Step 2.

Under the Hood — the five pool element types (skippable). A single_pool_element is one of five kinds of piece. The others: legacy_single_pool_element (an older single piece that keeps the world’s original blocks instead of placing air), feature_pool_element (places a placed feature, such as a tree or ore, in a 1×1×1 box), list_pool_element (places several pieces in sequence), and empty_pool_element (places nothing). For a one-building outpost you only need single_pool_element; the others matter once you build village-sized, multi-piece structures.

Step 2 — A processor list (aging the build)

A processor list “is used to transform blocks of a structure template during generation.” It’s a list of processors, and each processor is one rule for changing blocks as the piece is placed, for example making a fresh stone-brick build look weathered and broken. Processor lists live in data/<namespace>/worldgen/processor_list.

This step is optional, but it’s what makes a generated build look like it belongs in the world instead of looking brand-new. Let’s give the outpost a worn, half-ruined look:

data/mypack/worldgen/processor_list/outpost_aging.json

{
  "processors": [
    {
      "processor_type": "minecraft:block_age",
      "mossiness": 0.2
    },
    {
      "processor_type": "minecraft:block_rot",
      "integrity": 0.9
    }
  ]
}

Both processors come straight from the Processor list format:

  • minecraft:block_age — “Makes blocks aged.” Stone bricks get a chance to become cracked, mossy, or turned into stairs and slabs; obsidian can crack to crying obsidian. Its one field is mossiness: “the probability of using mossy variants when making a block aged” (clamped to the 0.0–1.0 range). We use 0.2 for a lightly mossy look.
  • minecraft:block_rot — “Randomly removes blocks.” Its field integrity is “the probability of randomly removing blocks in the structure,” a value between 0 and 1. We use 0.9, meaning each block has a 90% chance to survive (so about one in ten is knocked out, leaving gaps). Important detail from the docs: removed blocks “are not replaced by air.” They keep whatever was already in the world there, so the rot blends into the surroundings instead of leaving holes.

Now wire it into the pool from Step 1 by changing the one processors line:

data/mypack/worldgen/template_pool/outpost.json

{
  "fallback": "minecraft:empty",
  "elements": [
    {
      "weight": 1,
      "element": {
        "element_type": "minecraft:single_pool_element",
        "projection": "rigid",
        "location": "mypack:outpost",
        "processors": "mypack:outpost_aging"
      }
    }
  ]
}

Figure (to be captured). two copies of the outpost side by side — left brand-new, right aged with moss and a few missing blocks

Try It! There are more processors you can drop into the list. minecraft:gravity shifts blocks up or down “to fit the terrain like a village road” (handy for paths). minecraft:nop “does nothing”: useful as a placeholder. A few others (block_ignore, protected_blocks, capped) and a heavier rule-based processor exist too; read about the rule processor below before you reach for those.

The rule processor — for precise, conditional block swaps. Minecraft also has a minecraft:rule processor that swaps blocks based on tests (an input_predicate, location_predicate, position_predicate, an output_state, and an optional block_entity_modifier). It’s a small language of its own. This book teaches the simpler block_age/block_rot/gravity processors fully; when you need precise, conditional block swaps, open the Processor list page on the wiki or copy a vanilla processor list and adapt the rule you find there.

Step 3 — The structure definition

The structure definition is the file that says what your structure is. Don’t confuse it with the .nbt structure file from Chapter 40: that one is the saved blocks; this one is the JSON configuration. The wiki’s own words: a structure here “is a large decoration… configured using JSON files within a data pack in the path data/<namespace>/worldgen/structure. To generate in a world, a structure needs to be part of at least one structure set.”

data/mypack/worldgen/structure/outpost.json

{
  "type": "minecraft:jigsaw",
  "biomes": "#minecraft:is_overworld",
  "step": "surface_structures",
  "terrain_adaptation": "beard_thin",
  "spawn_overrides": {}
}

Each field traces to the Structure definition format:

  • type — “the ID of structure feature type.” Structures that build themselves out of template pools and jigsaw blocks use the jigsaw type, so we write minecraft:jigsaw. (See the box after this list; the jigsaw type needs more companion fields than the five universal ones shown here.)
  • biomes — “biomes that this structure is allowed to generate in.” This can be one biome ID, a list of IDs, or a biome tag (written with a leading #, the tag syntax from Chapter 14). We start broad with #minecraft:is_overworld so it can appear across the surface; we’ll narrow it to plains through the structure set in the next step.
  • step — “the step where the structure generates.” The allowed values are: raw_generation, lakes, local_modifications, underground_structures, surface_structures, strongholds, underground_ores, underground_decoration, fluid_springs, vegetal_decoration, and top_layer_modification. A surface building belongs in surface_structures.
  • terrain_adaptation — “the type of terrain adaptation used for the structure” (optional, defaults to none). The values: none (no adaptation), beard_thin (“generating terrain under the structure, while removing terrain inside the structure,” used by pillager outposts and villages), beard_box (an advanced version, ancient cities), bury (buries the structure, strongholds), and encapsulate (advanced bury, trial chambers). Since our outpost is modeled on the real pillager outpost, beard_thin is the natural choice: it lays a little foundation under the build so it doesn’t float on a hillside.
  • spawn_overrides — overrides which mobs spawn inside the structure (for example, how blazes spawn in nether fortresses, or how ancient cities block spawns). It is “required, but can be empty,” and an empty object means “don’t override anything; spawn based on the biome as normal.” We leave it empty with {}.

The jigsaw structure’s own fields. Setting type to minecraft:jigsaw is correct, but a real jigsaw structure definition needs several more jigsaw-specific fields to say which template pool it starts from and how big it may grow: the start pool, a maximum size, a starting height, and a few placement switches. Those fields shift between game versions, so rather than memorize a list, open the Structure page on the wiki for your version, or copy a vanilla jigsaw structure (a village or pillager outpost) and read its fields off the real file. Everything above (type, biomes, step, terrain_adaptation, spawn_overrides) is universal to every structure and is shown here in full.

Step 4 — The structure set (where it appears)

The structure set decides where the structure shows up across the world and how far apart copies are. It lives in data/<namespace>/worldgen/structure_set. As we saw, just having this file is what turns generation on.

data/mypack/worldgen/structure_set/outpost.json

{
  "structures": [
    {
      "structure": "mypack:outpost",
      "weight": 1
    }
  ],
  "placement": {
    "type": "minecraft:random_spread",
    "salt": 165745296,
    "spacing": 32,
    "separation": 8,
    "spread_type": "linear"
  }
}

The Java root has two parts, structures and placement (from the Structure set JSON format):

  • structures — “weighted list of structures that can be placed.” Each entry names a structure (our definition, mypack:outpost) and a weight (“determines the chance of it being chosen over others. Must be a positive integer”). With one structure, it’s always the one chosen.
  • placement.type — the placement type, “one of minecraft:concentric_rings or minecraft:random_spread.” We use random_spread, which spreads structures “evenly throughout the entire world,” the same scheme vanilla uses for most structures.
  • placement.salt — “a number that assists in randomization… must be a non-negative integer.” Two structure sets with the same spacing but different salt won’t land on top of each other. Pick any number; we used a big arbitrary one.
  • placement.spacing — for random_spread, “average distance between two neighboring generation attempts” in chunks, 0–4096. 32 means roughly every 32 chunks the game tries to place one.
  • placement.separation — “minimum distance (in chunks) between two neighboring attempts,” 0–4096, and it “has to be strictly smaller than spacing.” 8 keeps outposts at least 8 chunks apart. (If you ever set separation equal to or larger than spacing, nothing generates; see What Can Go Wrong.)
  • placement.spread_type — “linear or triangular” (optional, defaults to linear). linear picks the offset uniformly; triangular clusters offsets toward the middle of each cell, giving a more even-looking spread. We use linear.

That’s the whole machine. Now make it bite.

Loading it — reopen, don’t reload

Save all four files, then:

  1. Exit the world completely, all the way back to the title screen or server stop, then open it again. Worldgen registries only re-read on load. /reload will not pick up these files.
  2. Explore new land: fly out to chunks you’ve never visited, since only newly generated chunks can contain your structure.
  3. Speed it up with a locate. In a function:

data/mypack/function/find_outpost.mcfunction

locate structure mypack:outpost

Run it (call the function, or for a one-off you can type the same locate structure in chat). It points you to the nearest copy. Travel there and your aged little outpost should be standing in the landscape.

Figure (to be captured). the chat output of locate structure mypack:outpost showing coordinates, then the outpost found at that spot

Closing the Chapter 34 loop

Back in Chapter 34 you built a “forge cache” (a chest of themed loot) and wrote its loot table, but we deliberately left the structure for Part XI. This is that moment. If your outpost.nbt was saved with a chest inside it, you can point that chest at the Chapter 34 loot table so every generated outpost comes stocked. The mechanism is the one you already know from loot tables: a chest’s block entity carries a LootTable field naming the table to roll. You can bake that into the saved .nbt (set the chest’s loot table before you save the structure in Chapter 40), or a rule processor’s block_entity_modifier with minecraft:append_loot can attach a loot_table to placed block entities during generation.

Attaching loot during generation. The rule processor can append a loot table to a block entity: a block_entity_modifier with type: minecraft:append_loot and a loot_table field. That’s the generation-time route. The simplest, most reliable approach for this book, though, is to set the chest’s loot table inside the saved structure in Chapter 40 and let it ride along. If you want the generation-time append_loot route instead, copy a vanilla structure that stocks its chests this way and adapt the rule you find there.

How Jigsaw Blocks Connect Pieces

Our outpost was one piece, so we never had to make two buildings snap together. Real villages and pillager outposts are built from many small pieces joined by jigsaw blocks: “technical blocks commonly used as a way to construct large structures from smaller sections.” You’ll meet them the moment you go past a single building, so here’s how they work, from the Jigsaw Block page.

Each jigsaw block placed inside a saved structure carries a few settings (shown in its in-game interface, and stored as block-entity data):

  • Target Pool (pool) — the template pool to pick the next connecting piece from. This is the link that lets one piece pull in another.
  • Name (name) — this jigsaw block’s own name. A jigsaw “gets aligned with another structure’s jigsaw block that has this value in the target tag.”
  • Target name (target) — the name a connecting piece’s jigsaw must have to dock with this one. In short: my target must match your name for our two pieces to join.
  • Turns into (final_state) — “the block that this jigsaw block becomes” once generation is done. Jigsaw blocks shouldn’t be left visible in the finished build, so they’re typically set to turn into minecraft:air or whatever fits.
  • Selection Priority (selection_priority) and Placement Priority (placement_priority) — when a piece has several jigsaws that could all connect, “jigsaw blocks with higher selection priority get selected first,” and placement priority controls the order pieces process their own children.
  • Joint type (joint, when the jigsaw faces up or down) — rollable (the connecting piece is placed with a random rotation) or aligned (rotations are forced to match).

The flow, then: piece A has a jigsaw whose Target Pool points at a pool; the game picks a piece B from that pool; B is rotated and slid so that its jigsaw (whose name matches A’s target name) lines up against A’s; the jigsaw blocks turn into their final_state; repeat outward until the pool’s fallback terminates the branch.

Multi-piece jigsaw assembly in depth. The fields and connection idea above are the vocabulary, but a full multi-piece example (how a village-sized graph branches, the size and recursion limits, the exact order pieces resolve) is a bigger topic than one outpost needs. This book teaches the single-piece outpost completely and the connection rules above; building a sprawling multi-piece structure (where selection/placement priority and matched name/target tags really come into play) is its own project. When you’re ready to build a village, the best teacher is a vanilla one: open the village template pools and structure on the wiki, or unpack the vanilla data pack, and trace how its pieces chain together.

Try It! — structure tags. Beyond generation, structures can be grouped with structure tags (data/<namespace>/tags/worldgen/structure/..., the same tag idea from Chapter 14). Vanilla uses tags like on_treasure_maps and eye_of_ender_located to decide which structures explorer maps point to and which an eye of ender flies toward. Adding your outpost to a tag won’t change where it generates (the structure set does that), but it can hook it into those map/locating features.

Practice

  1. Build the outpost. Create all four files exactly as above (using your own .nbt name), reopen the world, and locate structure mypack:outpost to find one. Confirm the aging from the processor list shows up: look for moss and a few missing blocks.

  2. Make it rarer. In the structure set, raise spacing from 32 to 64 (keep separation smaller). Reopen the world and explore fresh land; outposts should now be noticeably farther apart. Remember: already-generated chunks won’t change.

  3. Pin it to plains. Change the structure definition’s biomes field from #minecraft:is_overworld to a single plains biome ID, minecraft:plains (a plain string instead of a #tag). Reopen and confirm new outposts only appear on plains.

  4. Heavier ruin. In the processor list, lower block_rot’s integrity from 0.9 to 0.7 (more blocks removed) and raise block_age’s mossiness to 0.5. Reopen and compare; your outpost should look much more weathered.

  5. (Stretch) Wire in the Chapter 34 cache. If your .nbt has a chest, set its loot table to your Chapter 34 cache table before saving the structure in Chapter 40, so every generated outpost is stocked. Then locate one and open the chest.

What Can Go Wrong

“I edited a worldgen file and ran /reload, and nothing changed.” Worldgen files are dynamic registries; /reload doesn’t touch them. Exit the world and reopen it. And even then, only new chunks reflect the change, so fly out to unexplored land to see it.

“My structure never appears anywhere.” Two usual causes. First, a biome mismatch: the structure’s biomes field must include a biome that actually exists where you’re looking. If you set it to a biome you never visit, you’ll never see the structure. Second, bad placement math: in random_spread, separation must be strictly smaller than spacing. If separation is equal to or larger than spacing, the game can’t fit any attempts and nothing generates. Also double-check the structure set file is actually present: its mere existence is what switches generation on.

“I copied a structure-set example and the game rejected the file.” You may have grabbed the Bedrock form. The wiki shows a structure_set layout that starts with format_version and wraps everything in minecraft:structure_set with a description/identifier. That’s the Bedrock add-on format. A Java data pack uses the flat root shown in Step 4: a top-level structures list and a placement object, no format_version, no description. Make sure you’re using the Java shape.


What You Know Now (Part XI so far)

You can now make a saved building generate by itself in the world. You know the four worldgen files that cooperate to do it: structure definition (what/where-biome/step/terrain/spawns), template pool (which pieces, with weights and rigid/terrain_matching projection), processor list (aging and altering blocks as they place), and structure set (random spread vs. concentric rings, with spacing/separation/salt). You also know that just having the structure-set file turns generation on. You understand that all four are dynamic registries that need a world reopen, not /reload, and that only new chunks reflect changes. You can connect the jigsaw block vocabulary (pool / name / target / final_state / priorities) that larger multi-piece structures rely on. And you’ve closed the Chapter 34 loop: the forge-cache chest can finally live inside a real, self-generating outpost.

You can now build a custom self-generating structure in chosen biomes, at a spacing you control, made to look aged and lived-in. That is what every world-generation pack is built on.

Chapter 42 — Biome Customization

What You’ll Build

A biome is a region of a generated world with its own distinct geography, plants, mobs, temperature, humidity, and colors: forests, deserts, oceans, the Nether wastes. The biome of a location is determined during world generation rather than by the current environment. That last part is the key to this whole chapter, and we’ll come back to it.

Until now you’ve added files to registries that take effect the moment you /reload: recipes, loot tables, advancements, tags. World generation is different. In this chapter you’ll write a biome definition (a JSON file under data/<namespace>/worldgen/biome/) that defines a brand-new biome called the Frozen Wasteland: bone-cold temperature, falling snow, pale washed-out water and grass, and a spawners block that fills it with hostile mobs. You’ll learn the full Java biome-definition field shape, why a biome needs a world reboot rather than a /reload, how biome tags group biomes and what they’re used for, and finally what those cat/variant and wolf/variant item components from Chapter 24 actually point at: the mob-variant definition registries.

By the end you’ll have a registered custom biome (whether or not it shows up in your world yet, that comes in Chapter 44), and you’ll understand exactly how far the standard fields take you and where you’d reach for the live wiki to go further.

Figure (to be captured). a player standing in a custom pale-blue snowy biome with washed-out grass, a skeleton and a husk visible nearby

Modern Minecraft. Worldgen files used to live behind the dreaded “experimental” warning. In the registry map you saw in Chapter 38, the worldgen/biome folder (like every worldgen folder) carries a small red asterisk. That asterisk means a pack using that folder is flagged as using experimental settings (you’ll get a warning screen when you create the world, and Realms won’t take it). The feature itself is real and documented; it’s the folder that trips the flag, exactly like the dialog folder back in Chapter 39.

What a Biome Is (and Real Biome Names, At Last)

Twice before, this book has used a biome ID and then quietly admitted it couldn’t fully prove it. In Chapter 18 we wrote a predicate that fired in the dark forest and hedged the ID minecraft:dark_forest. In Chapter 31 we avoided biome literals entirely. This chapter closes that gap, naming the real biomes directly.

In Java Edition there are 66 biome types: 55 for the Overworld, 5 for the Nether, and 5 for the End, plus one used only for a superflat preset. Each biome has its own resource location (its ID) of the familiar minecraft:<name> shape. Dozens of these biomes have names you’ll recognize: the plains, the dark forest, snowy plains, ice spikes, deserts, swamps, jungles, cherry groves, the pale garden, and so on, all real biomes you’ll see in-game. There’s an important difference between knowing a biome’s name and knowing its exact ID. The wiki’s Biome/ID page lists every Java biome beside its exact resource location (more on that in a moment). Two of those IDs you can confirm a second way, which makes them worth calling out:

  • minecraft:dark_forest — the dark forest biome is mainly composed of dark oak trees (woodland mansions can generate here), and dark_forest is one of the allowed values of the grass_color_modifier field you’ll meet below. The registry name dark_forest shows up verbatim as a field value, so this one is solidly grounded, and that lays the Chapter 18 hedge to rest.
  • minecraft:plains — appears as a full ID literal in the game’s feature-ordering rules (in the UNDERGROUND_ORES step of minecraft:plains, ore_dirt is placed before ore_gravel). So plains is a confirmed ID too.

Resolving the gap (for real this time). The wiki’s Biome/ID page carries the complete Java Edition table: every biome paired with its exact resource location, from minecraft:the_void and minecraft:plains through minecraft:dark_forest, minecraft:cherry_grove, minecraft:deep_dark, the Nether five (nether_wastes, warped_forest, crimson_forest, soul_sand_valley, basalt_deltas), and the End set (the_end, end_highlands, end_midlands, small_end_islands, end_barrens). So the Chapter 18 hedge is fully closed: minecraft:dark_forest is real, and so is every other ID you’ll reach for. Two of them you can double-confirm a second way: dark_forest is also an allowed grass_color_modifier value, and plains appears as a literal ID in the feature-ordering example above. (One genuine caution worth keeping: the strings minecraft:swamp, minecraft:frozen_ocean, and minecraft:the_end also appear as Bedrock surface_builder type values, so a name matching a biome isn’t proof of the biome’s ID. The Biome/ID table is the place to confirm the Java resource location.)

What actually makes a biome feel like a biome? A handful of properties do, and these are exactly the fields you’re about to write:

  • Temperature — a number that drives grass and foliage color, and (height-adjusted) whether it rains or snows.
  • Downfall — a humidity number between 0.0 and 1.0, mainly used for block colors; above 0.85 the biome counts as “humid.”
  • Precipitation — in Java Edition, simply on or off (true/false), separate from downfall.
  • Effects — the colors of water, grass, and foliage.
  • Spawns — which mobs appear, and how often.
  • Features and carvers — the trees, ores, lakes, and caves carved into the terrain (Chapter 43).

The Biome Definition File

Biome definitions are stored as JSON files within a data pack in the path data/<namespace>/worldgen/biome. A file at data/mypack/worldgen/biome/frozen_wasteland.json therefore becomes the biome mypack:frozen_wasteland: the same registry-folder rule you’ve used since Chapter 7, just a deeper folder.

Here is every Java field, top to bottom. Read it once now; the next section builds a real file using these.

Root fields:

  • has_precipitation — a true/false boolean: whether the biome has precipitation at all.
  • temperature — a float (decimal number) that “controls gameplay features like grass and foliage color, and a height adjusted temperature (which controls whether raining or snowing occurs if has_precipitation is true).”
  • temperature_modifier — optional, defaults to none. Either none or frozen. When frozen, it “makes some places’ temperature high enough to rain (0.2)”. This is the trick the frozen ocean uses so a few patches don’t freeze.
  • downfall — a float that “controls grass and foliage color.”
  • effects — a compound (an object) holding the biome’s ambient colors. Its fields:
    • water_color — required, “the normal value is 4159204.” A decimal number converted from a hex color, used for water blocks and cauldrons.
    • foliage_color — optional. Decimal color for tree leaves and vines. If absent, it’s derived from downfall and temperature.
    • dry_foliage_color — optional. Decimal color for leaf litter.
    • grass_color — optional. Decimal color for grass blocks, grass, ferns, and sugar cane. If absent, derived from downfall and temperature.
    • grass_color_modifier — optional, defaults to none. One of none, dark_forest, or swamp (these apply the special grass tints those biomes use).
  • carvers — required, but can be empty. The cave/ravine carvers for this biome. We’ll write {} and leave carvers for Chapter 43.
  • features — a list of generation steps (can be empty). Each step is itself a list of placed features to run during that step. There are eleven step names, in order: RAW_GENERATION, LAKES, LOCAL_MODIFICATIONS, UNDERGROUND_STRUCTURES, SURFACE_STRUCTURES, STRONGHOLDS, UNDERGROUND_ORES, UNDERGROUND_DECORATION, FLUID_SPRINGS, VEGETAL_DECORATION, and TOP_LAYER_MODIFICATION. (You build the things that go inside these steps in Chapter 43.)
  • creature_spawn_probability — optional float between 0.0 and 0.9999999; higher means more creatures spawned during world generation.
  • spawners — required, but can be empty. This is the heart of “what spawns here.” It’s a compound whose keys are mob categories and whose values are lists of spawn entries.
  • spawn_costs — required, but can be empty. Only mobs listed here use the “spawn cost” mechanism (a way to limit dense spawning); each entry has an energy_budget and a charge.

Inside spawners — the mob categories. Each key must be one of monster, creature, ambient, water_creature, underground_water_creature, water_ambient, misc, or axolotls. If a category is missing or its list is empty, mobs in that category simply don’t spawn. Each entry in a category’s list is the spawner data for a single mob:

  • type — the namespaced entity ID of the mob (e.g. minecraft:skeleton).
  • weight — an int: how often this mob spawns; higher values produce more spawns.
  • minCount — an int greater than 0: the minimum size of a spawned pack.
  • maxCount — an int not less than minCount: the maximum pack size.

Under the Hood (skippable). Notice minCount and maxCount use capital letters in the middle. That’s “camelCase,” and it’s unusual for data-pack JSON, which almost always uses lowercase snake_case. Worldgen inherited a few old names like these. Type them exactly as shown; JSON cares about capitalization.

Those are the effects fields we’ll use here. Real biomes also carry a few more effect fields: a sky_color, a fog_color and water_fog_color, a mood_sound and additions_sound, music, and ambient particles. They drive the daytime sky tint (computed from temperature), the fog color, the cave ambience, and the pale garden’s silence. We’ll build a vivid biome with the color fields alone, which is plenty to see the system working; when you want to add the rest, open the wiki’s Biome page or copy a vanilla biome and write the extra fields you find there.

Walkthrough: The Frozen Wasteland

Let’s build it. We need a single file. Create the folders worldgen/biome inside your mypack data folder and add this file.

data/mypack/worldgen/biome/frozen_wasteland.json

{
  "has_precipitation": true,
  "temperature": -0.7,
  "temperature_modifier": "none",
  "downfall": 0.4,
  "effects": {
    "water_color": 3750201,
    "foliage_color": 11445290,
    "grass_color": 8434339
  },
  "carvers": {},
  "features": [],
  "creature_spawn_probability": 0.05,
  "spawners": {
    "monster": [
      {
        "type": "minecraft:skeleton",
        "weight": 100,
        "minCount": 1,
        "maxCount": 4
      },
      {
        "type": "minecraft:husk",
        "weight": 80,
        "minCount": 1,
        "maxCount": 3
      }
    ],
    "creature": []
  },
  "spawn_costs": {}
}

Walk through it against the field list:

  • "has_precipitation": true with a very low "temperature": -0.7 means precipitation falls as snow: when the base temperature is below 0.15, a biome is snowable at any height. That’s our frozen wasteland’s weather.
  • temperature_modifier is none here. (Set it to frozen if you wanted scattered unfrozen patches like the frozen ocean; we want it uniformly icy.)
  • The three effects colors are decimal numbers. 3750201 is a deep cold-blue water; 11445290 and 8434339 are pale, washed-out foliage and grass. These are ordinary decimal-from-hex color numbers, exactly the kind you’ve converted since Chapter 21’s custom_model_data colors. water_color is the one effect field that’s required, so it must be present.
  • "carvers": {} and "features": [] are the empty-but-required containers. Our biome generates with no special caves and no trees or ores of its own: a true wasteland. (Chapter 43 fills these.)
  • creature_spawn_probability is low (0.05) because a wasteland should feel barren of passive life.
  • spawners is where the danger lives. The monster category lists skeletons (weight 100) and husks (weight 80), each spawning in small packs. weight is relative: skeletons appear a bit more often than husks. The creature list is present but empty, so no passive animals spawn here. We left the other six categories out entirely, which means those categories don’t spawn either.
  • spawn_costs is the required-but-empty {}; we’re not using the spawn-cost limiter.

Every field above is a real biome-definition field. Save the file.

Try It! Want a hint of life among the bones? Add a third category to spawners: "ambient": [ { "type": "minecraft:bat", "weight": 10, "minCount": 1, "maxCount": 2 } ]. Bats are in the ambient category, so they belong there, not in creature.

Why a /reload Isn’t Enough

Now the catch we flagged at the top. A location’s biome is determined during world generation rather than by the current environment, even if every block in a large area is altered to imitate the terrain of another biome. A biome is baked into the world as it generates.

This connects to the dynamic-registry idea from Chapter 9. Most registries you’ve written to (recipes, loot tables, functions) are re-read every time you run /reload. Worldgen registries (biomes, dimensions, and the enchantments you saw in Chapter 35) are dynamic registries that are loaded when a world is created or opened, not on /reload. Editing frozen_wasteland.json and typing /reload will not make the biome appear or update.

To get a biome definition into the registry:

  1. Make sure your pack (with the worldgen/biome file) is installed in the world’s datapacks folder before you open the world. The cleanest way: put the pack in a fresh world’s datapacks folder and create the world.
  2. If you edit the file later, you must close and reopen the world (or remake it) for the change to load, not /reload.

What Went Wrong? “I made the biome, ran /reload, and /locate biome mypack:frozen_wasteland says it doesn’t exist.” A /reload doesn’t reload worldgen. Quit to the title screen and reopen the world. If it still isn’t found, the file has a JSON typo and failed to load. Check the game log (Chapter 10) for a worldgen error naming your file.

And one honest limitation: registering a biome is not the same as making it appear in your Overworld. The Overworld decides which biomes go where using a biome source that’s part of its dimension. A freshly registered custom biome sits in the registry, ready, yet won’t show up until a dimension is told to place it. That’s Chapter 44’s job. For now you can still prove the biome loaded:

data/mypack/function/find_frozen_wasteland.mcfunction

locate biome mypack:frozen_wasteland

In a vanilla Overworld this will report the biome isn’t found nearby (because nothing places it yet), but if it loaded into the registry the command will be recognized rather than erroring on an unknown biome, your first sign the file is valid. (You’ll make it actually generate in Chapter 44.)

Biome Tags and Their Uses

You’ve grouped registry entries with tags since Chapter 14. Biomes get tags too. A biome tag is simply a group of biomes, and it has three concrete uses:

  1. Controlling where structures generate.
  2. Setting the spawn conditions of various entities, including, as you’ll see in a moment, which variant of an animal spawns.
  3. Testing biomes in commands. A biome tag can be used when testing for biome arguments in commands with #<resource location>, which succeeds if the biome matches any of the biomes specified in the tag. So #minecraft:is_forest matches every forest biome.

Real biome tags include is_overworld, is_forest, is_badlands, is_ocean, is_taiga, is_mountain, is_river, is_nether, is_end, is_jungle, and is_beach. A biome-tag file looks like any other tag file (Chapter 14), a list of biome IDs, and lives at data/<namespace>/tags/worldgen/biome/<name>.json.

Try It! Make your own biome group and put the wasteland in it:

data/mypack/tags/worldgen/biome/spooky.json

{
  "replace": false,
  "values": [
    "mypack:frozen_wasteland"
  ]
}

Now #mypack:spooky is a valid biome test in any command that takes a biome argument. (Like the biome itself, this is worldgen-adjacent data, so reopen the world after adding it.)

Mob-Variant Definition Registries

Back in Chapter 24 you met a quietly important kind of item component: the minecraft:<mob>/variant strings. A cat carries minecraft:cat/variant; a wolf carries minecraft:wolf/variant and a separate minecraft:wolf/sound_variant; cows, chickens, frogs, and pigs each have their own. We told you those strings name a variant but deferred where the variant is defined to this chapter. Here it is.

In modern Minecraft, a mob’s appearance variants are their own dynamic registries, each with its own data-pack folder, right alongside worldgen/biome in the registry map you read in Chapter 38. Here they are, every one marked with the experimental red asterisk:

data/<namespace>/
  cat_variant              * Textures and spawn conditions of cat variants
  chicken_variant          * Textures and spawn conditions of chicken variants
  cow_variant              * Textures and spawn conditions of cow variants
  frog_variant             * Textures and spawn conditions of frog variants
  pig_variant              * Textures and spawn conditions of pig variants
  wolf_variant             * Textures and spawn conditions of wolf variants
  wolf_sound_variant       * Sound variants of wolves
  zombie_nautilus_variant  * Textures and spawn conditions of zombie nautilus variants

So the wiring is: a file at data/mypack/wolf_variant/glacier.json would register the wolf variant mypack:glacier, and the item component minecraft:wolf/variant on a wolf is a string that points at that ID. The component side matches exactly: for the wolf, minecraft:wolf/variant is a wolf variant definition (the variant of the wolf), and likewise for cat/variant, chicken/variant, cow/variant, frog/variant, and pig/variant. The phrase “spawn conditions” in the table above is the link back to biomes: the game uses biome tags to decide which variant spawns where. The deciding tags are right there in the biome-tag list: spawns_cold_variant_farm_animals, spawns_warm_variant_farm_animals, spawns_cold_variant_frogs, spawns_warm_variant_frogs, and spawns_coral_variant_zombie_nautilus. That’s how a cow “knows” to be the warm variant in a jungle and the cold variant in a taiga: the biome’s tags steer it.

You can set a variant directly on a spawned mob, using the very component from Chapter 24. Here’s a test function that gives you a wolf already locked to a specific variant via its component:

data/mypack/function/give_test_wolf.mcfunction

give @s minecraft:wolf_spawn_egg[minecraft:wolf/variant="minecraft:pale"]

The minecraft:wolf/variant component holds a string, a variant ID. Here it’s a vanilla one. If you had authored mypack:glacier in wolf_variant/, you’d write minecraft:wolf/variant="mypack:glacier" instead, and a wolf spawned from that egg would wear your variant.

So you’ve seen the folders, the item components that reference them, and the biome tags that steer them. The one piece left is the inside of a variant definition file: which textures it points at, which spawn-condition keys it uses, its asset model fields. Those internal fields are the variant system’s own deep end: for cat, chicken, cow, frog, pig, wolf, wolf-sound, and zombie-nautilus variants, when you want to author a full custom variant, open the live wiki’s “Mob variant definitions” page or copy a vanilla variant file and write the fields you find there. (One handy exception: minecraft:horse/variant isn’t a definition file at all but a fixed list of values: white, creamy, chestnut, brown, black, gray, or dark_brown.)

Modern Minecraft. If you followed older tutorials, you may have seen cat or wolf variants set with raw NBT, or grouped with a cat_variant tag. Those tags still exist for compatibility, but the old default_spawns / full_moon_spawns cat-variant tags were replaced by spawn condition: the modern variant system uses spawn conditions (and biome tags) for this grouping now.

Practice

1. A second biome — Scorched Flats. Build the opposite of the wasteland: blistering, dry, no precipitation. Reuse the exact field shape from the walkthrough.

data/mypack/worldgen/biome/scorched_flats.json

{
  "has_precipitation": false,
  "temperature": 2.0,
  "downfall": 0.0,
  "effects": {
    "water_color": 4566514,
    "foliage_color": 10387789,
    "grass_color": 12431967,
    "grass_color_modifier": "none"
  },
  "carvers": {},
  "features": [],
  "creature_spawn_probability": 0.0,
  "spawners": {
    "monster": [
      {
        "type": "minecraft:husk",
        "weight": 100,
        "minCount": 2,
        "maxCount": 4
      }
    ]
  },
  "spawn_costs": {}
}

Note "has_precipitation": false and "downfall": 0.0 (no rain or snow at all) and a temperature of 2.0, the kind of value deserts use. We dropped temperature_modifier entirely (it’s optional and defaults to none), creature_spawn_probability is 0.0, and only husks spawn. Confirm it loads with locate biome mypack:scorched_flats after reopening the world.

2. Tag both your biomes. Extend data/mypack/tags/worldgen/biome/spooky.json (or make a new harsh.json) to list both mypack:frozen_wasteland and mypack:scorched_flats, then use #mypack:harsh as a biome test in a command of your choice.

3. Read a variant’s wiring. Without authoring a variant definition (the book can’t), write a give function that hands you a cat spawn egg whose minecraft:cat/variant component is set to a vanilla variant ID of your choice. You’re practicing the component → variant-ID link from Chapter 24, now that you know what the ID refers to.

What Can Go Wrong

  • You typed /reload and nothing happened. Worldgen is a dynamic registry. Biome (and variant) files load when the world is created or opened, never on /reload. Quit to the title and reopen the world.
  • You used a hex color string for water_color. The color fields are decimal integers, not "#aabbcc" strings. Convert your hex to a decimal number first (the same conversion you’ve done for item colors since Chapter 21). water_color is also required; leaving it out fails the file.
  • You put a passive animal in the monster category (or invented a category). The category key must be exactly one of the eight valid keys (monster, creature, ambient, water_creature, underground_water_creature, water_ambient, misc, axolotls). A typo’d category name silently means “nothing spawns there.”
  • You expected the biome to appear in your Overworld. Registering a biome doesn’t place it. A dimension’s biome source decides where biomes generate: that’s Chapter 44. Until then, locate recognizing the ID is your proof of a valid file.
  • You tried to write the inside of a wolf_variant file from this book. This chapter stops at the folder boundary for those internal fields. Name and reference variants with confidence; author their definition bodies from the live wiki.

Chapter 43 — Features and Placed Features

What You’ll Build

Look closely at any Minecraft world and you’ll see it’s covered in small, scattered details: a clump of flowers here, a tree there, a blob of iron ore deep underground, an amethyst geode hidden in a cave, a little lake of water sitting in a hollow. The game calls each of these a feature: a single generated decoration. In the last chapter you built a biome, and you saw it had a features field that you mostly left alone. This chapter is about what actually goes in that field.

By the end you’ll understand the two files that work together to put a feature in the world: a configured feature (the what: which kind of feature, with which settings) and a placed feature (the where: that configured feature plus a list of rules deciding how many appear, how high up, and in which biomes). You’ll write a real placed feature, gate it to specific biomes and a height band, test it instantly with the placefeature command, and wire it into a biome so it generates naturally. You’ll also build a placement wrapper for a giant-mushroom feature.

There’s one honest catch you’ll meet head-on in this chapter, and it’s worth saying up front: this chapter teaches the placement machinery in complete detail, but the inner settings of the individual feature types (the exact knobs on ore, tree, geode, and friends) are deep enough to be their own topic. So you’ll learn to do everything around a configured feature (and reference the ones the game already ships) while we point you to the wiki for the one piece you’ll look up for your exact version. Knowing precisely where the edge of your knowledge is, is itself a skill.

Figure (to be captured). a custom band of ore generating only in a snowy biome, shown via the F3 debug screen with the biome name visible

Two files, one idea: what and where

A feature in Minecraft is split across two ideas, and almost every confusion about world generation comes from mixing them up. Keep them separate and the whole system falls into place.

Feature. A single thing the world generator places: a tree, a blob of ore, a patch of flowers, a geode, a lake. Features are the things that get placed in a world.

Configured feature. The what. A configured feature is the configuration of a feature type: it picks a feature type (the kind of feature) and fills in its settings. It does not say where in the world it goes.

Placed feature. The where. A placed feature determines where a configured feature should be attempted to be placed, using placement modifiers. It wraps a configured feature in a list of rules. Placed features can be referenced in biomes. This is the file a biome actually points at.

So the chain is: a placed feature points at a configured feature, and a biome points at the placed feature. Three links. The configured feature knows how to build one copy of the thing; the placed feature decides how many and where; the biome decides which biome gets it.

Each kind lives in its own folder inside your data pack, right next to the worldgen/biome/ folder you made in Chapter 42:

data/mypack/worldgen/
  biome/                 the biomes you built in Chapter 42
  configured_feature/    the "what" files  (one feature + its settings)
  placed_feature/        the "where" files (a configured feature + placement rules)

Both paths are exact: configured features are stored as JSON files within a data pack in the data/<namespace>/worldgen/configured_feature folder, and placed features are stored the same way in the worldgen/placed_feature folder.

Feature types are hardcoded — and that matters

Here is a rule that surprises people: you cannot invent a new kind of feature. A feature type determines how and what a configured feature should generate, and feature types are hardcoded: new ones cannot be added through data packs. The game ships a fixed set of feature-type builders (the code that knows how to grow a tree, scatter a flower patch, hollow out a geode), and a data pack’s job is only to configure one of those builders, never to write a brand-new one.

This is different from most of what you’ve done in this book. A recipe or a loot table is content you write from scratch. A configured feature is more like filling in a form for a machine that already exists: you choose the machine (the type) and set its dials (the config).

Modern Minecraft. Old tutorials sometimes talk about “custom structures” and “custom features” as if they were the same thing. They aren’t. Features are the small natural decorations covered in this chapter (ores, trees, patches). Structures (villages, temples, mineshafts) are a separate system with their own folder. This chapter is only about features.

The configured feature file (and where to look up the inner config)

The configured feature file has a simple outer shape. The whole root format is just:

  • a root object with two fields
  • type — a string: the ID of the feature type
  • config — a compound (object): the configuration of this configured feature, whose properties depend on the value of type.

That config’s contents depend on which type you chose. An ore-type feature’s config has dials about which blocks to replace and how big the blob is; a tree-type feature’s config has dials about the trunk and leaves; and so on. Here is where this chapter draws its line:

The outer wrapper (type + config) is the same for every feature, and feature types are hardcoded. The individual feature types and the fields inside their config objects are a deep, type-by-type topic. The exact fields for the ore, tree, random_patch, geode, or lake configs each differ. So where you need a real configured feature in this chapter, we’ll reference one the game already ships (a vanilla configured feature), and when you want to author a new one from scratch, open that feature type’s page on the live wiki (or copy a vanilla example) and write the fields you find there for your exact version.

This isn’t as limiting as it sounds, because vanilla already ships hundreds of configured features (every ore blob, every tree, every flower patch you see in a normal world is one), and your placed feature can point straight at them by their ID. You’ll do exactly that in the walkthrough. Some feature types are even “configuration-less features”: they have a file in the configured_feature folder but no settings at all.

Under the Hood (skippable). The reason the config shape changes with type is that each feature type is a separate piece of Java code with its own settings object. The data pack just hands that code a blob of JSON shaped the way that particular type expects. It’s the same “the fields depend on the type” pattern you’ve seen in predicates and in placement modifiers below. Minecraft uses it all over world generation.

The placed feature file

The placed feature is where this chapter does its real work, and happily it’s documented in full. Its root format:

  • a root object with two fields
  • feature — the feature to place. This is a reference to a configured feature: either its ID (a string like minecraft:ore_iron) or an entire configured feature written inline as an object.
  • placement — a list of placement modifiers, applied in order. Each entry is an object with a type string, and its other fields depend on the value of type.

So a placed feature is a configured feature plus an ordered list of small rules. Those rules are called placement modifiers.

What the placement list actually does

This is the most important paragraph in the chapter, so read it slowly. When a placed feature is reached through a biome, it starts by trying to place its configured feature once, at the northwest corner of each chunk, at the bottom of the world. That’s the starting point: one attempt, one position, at the very bottom of a 16×16 chunk column. The placement modifiers then run in order, and each one can do one of three things to the position(s):

  1. Move a position (e.g. raise it to a sensible height).
  2. Multiply positions (e.g. turn one attempt into twelve).
  3. Filter positions out (e.g. drop any that aren’t in the right biome).

Put plainly: placement modifiers can change the position of the feature and the amount of placements, applied in order to determine where feature placement attempts should occur, and each placement attempt applies the placement modifiers separately. So a typical list reads like a little recipe: make several attempts → spread them around the chunk → pick a height → keep only the ones in the right biome. You build the behavior you want by stacking these small rules.

Modern Minecraft. If you followed a very old tutorial, you may have seen worldgen “decorators” with a single configured shape. The current system splits that into the configured feature and this ordered list of placement modifiers. If a tutorial talks about a decorated feature type or a decorator field, it is out of date.

There are two other ways a placed feature can be reached, which is useful to know: when it’s referenced from inside another configured feature, or through the placefeature command, it starts at the feature’s (or player’s) own position instead of the chunk corner. You’ll use the command path to test things in a moment.

The placement-modifier catalogue

Every placement-modifier type below comes with its real fields. There are a lot of them; you don’t need all of them at once. We’ll group them by the three jobs from above so you can find the right tool quickly.

Modifiers that multiply (make more attempts)

  • count — “Returns multiple copies of the current block position.” Field: count, a number between 0 and 4096. (It can also be a small object for a random count, see the note below.) Stacking several count modifiers multiplies, so you can exceed 4096.
  • count_on_every_layer — like count, but it places on each horizontal layer separated by air, lava, or water in the chunk. Field: count (0–256). Good for cave decorations on every ledge.
  • noise_based_count and noise_threshold_count — make the count depend on a noise value, so density varies smoothly across the world. These have several numeric fields (noise_factor, noise_offset, noise_to_count_ratio, or noise_level/below_noise/above_noise). You’ll rarely reach for them as a beginner.

A note on the count value. count’s value can be an int or a compound. A plain number means “exactly this many.” The object form lets it be a random range. Minecraft calls that an int provider. We’ll only use the plain-number form in this chapter. If you ever need a random count, look up the int-provider format on the wiki for your version and use the object form there.

Modifiers that move (change the position)

  • in_square — for both X and Z, it adds a random value between 0 and 15. No fields. This is the workhorse: it spreads your attempts randomly across the 16×16 chunk instead of all landing on the corner. Almost every placed feature uses it.
  • height_range — sets the Y coordinate to a value provided by a height provider. Field: height, a height provider (covered just below). This is how you say “somewhere between Y=20 and Y=60.”
  • heightmap — sets the Y coordinate to one block above the heightmap. Field: heightmap, one of MOTION_BLOCKING, MOTION_BLOCKING_NO_LEAVES, OCEAN_FLOOR, OCEAN_FLOOR_WG, WORLD_SURFACE, or WORLD_SURFACE_WG. Use this for surface features (trees, flowers): it drops the feature onto the ground instead of leaving it at a fixed height. Roughly, WORLD_SURFACE is the top block including trees and leaves, and OCEAN_FLOOR is the top solid block ignoring water. But check the precise definitions of each heightmap name on the wiki for your version.
  • random_offset — nudges the position by an amount. Fields: xz_spread and y_spread (each −16 to 16). Despite the name, the offset is only random if you give it a random provider; a fixed number always shifts by that exact amount.
  • fixed_placement — places at exact listed positions. Field: positions, a list of [x, y, z] triples. Used when you want a feature at known coordinates.

Height providers and vertical anchors

height_range’s height field is a small object called a height provider. The type is one of: constant, uniform (random, even spread), biased_to_bottom and very_biased_to_bottom (random, leaning low, great for ores), trapezoid (random, leaning toward the middle), and weighted_list. The common ones, uniform and the biased pair, take a min_inclusive and a max_inclusive, each a vertical anchor.

Vertical anchor. How a Y value is written. There are three forms: absolute (a flat Y like the F3 screen shows), above_bottom (counting up from the world floor), and below_top (counting down from the world ceiling). So { "absolute": 40 } means Y=40, and { "above_bottom": 8 } means 8 blocks above the bottom of the world.

A uniform height between Y=16 and Y=64 therefore looks like this:

{
  "type": "minecraft:uniform",
  "min_inclusive": { "absolute": 16 },
  "max_inclusive": { "absolute": 64 }
}

Modifiers that filter (drop positions)

  • biome — returns the current position if the biome at that position includes this placed feature, otherwise returns empty. No fields. This is the one that keeps a feature inside the biomes that asked for it, so it doesn’t bleed across biome borders. Read the warning box below; this one has a sharp edge.
  • rarity_filter — keeps a position with probability 1 / chance. Field: chance, a positive integer. "chance": 32 means “on average, one in 32 attempts survives.” It’s your main tuning knob for “how rare is this thing.”
  • block_predicate_filter — keeps the position only if a block predicate passes. Field: predicate (covered below). Use it for “only place on stone” or “only place where there’s air above.”
  • surface_water_depth_filter — keeps the position only if the water above the surface is shallower than max_water_depth. Good for keeping land features out of deep ocean.
  • surface_relative_threshold_filter — keeps the position only if it’s within a height range relative to the surface (heightmap, min_inclusive, max_inclusive). For “just under the surface” effects.
  • environment_scan and carving_mask — advanced. environment_scan walks up or down until a block predicate matches (fields direction_of_search, max_steps, target_condition, optional allowed_search_condition); carving_mask returns positions carved out by a carver (field step, air or liquid). You’ll meet carvers in Chapter 45.

What Went Wrong? The biome modifier can crash your world. Here’s a real warning worth heeding: the biome modifier cannot be used in placed features that are referenced from other configured features. If you do it anyway, Minecraft does not catch this type of error automatically on trying to load the world; instead the game runs normally until it tries to generate the feature, which causes the game to crash. So biome is safe in a placed feature that a biome points at (the normal case), but never put it in a placed feature that’s nested inside a configured feature. Symptom: a crash the first time that chunk tries to generate, not at load.

Block predicates (for block_predicate_filter)

A block predicate is a test for the state of a block at a given position in the world. Like everything in worldgen, it’s a type plus type-specific fields. The useful ones:

Block predicate. A small test object. These are the types you’ll reach for (among others): true (always matches), all_of / any_of (combine child predicates), not (invert a predicate), matching_blocks (the block is one of a given list/tag, field blocks), matching_block_tag (the block is in a given block tag, field tag), solid (the block is solid), replaceable (the block can be replaced, e.g. air/grass), and would_survive (a given block state could legally be placed here). Each also takes an optional offset ([X,Y,Z], each −16 to 16) so it can test a neighbouring block, e.g. “is the block below solid?”.

So “only place where the block below is solid ground” is a block_predicate_filter whose predicate is a solid test with offset [0, -1, 0].

Getting the feature into the world: decoration steps

You now have a placed feature. How does it actually generate? Through a biome’s features field, the same field you saw in Chapter 42 and left alone. Now you can fill it in.

The features field is a list of generation steps, usually 11 of them. It’s therefore a list of lists: an outer list with one slot per decoration step, and each slot holds the placed features that run during that step. The steps run in a fixed order, and each has a job. Here they are in order:

features:  (a list of 11 steps, in this order)
  [0]  RAW_GENERATION            small end-island features
  [1]  LAKES                     lava lakes
  [2]  LOCAL_MODIFICATIONS       amethyst geodes, icebergs
  [3]  UNDERGROUND_STRUCTURES    dungeons, fossils
  [4]  SURFACE_STRUCTURES        desert wells, blue-ice patches
  [5]  STRONGHOLDS               (not used for features in vanilla)
  [6]  UNDERGROUND_ORES          ore blobs, dirt/gravel disks
  [7]  UNDERGROUND_DECORATION    infested blocks, nether ore/gravel blobs
  [8]  FLUID_SPRINGS             water and lava springs
  [9]  VEGETAL_DECORATION        trees, bamboo, cacti, kelp, vegetation
  [10] TOP_LAYER_MODIFICATION    surface freezing (snow/ice)

This list answers the chapter’s questions about which feature goes where: a geode belongs in step LOCAL_MODIFICATIONS, an ore in UNDERGROUND_ORES, a tree in VEGETAL_DECORATION, a lava lake in LAKES. The position in the outer list is the step, so an ore feature goes in the 7th slot (UNDERGROUND_ORES).

What Went Wrong? The cross-biome ordering rule. There’s a subtle constraint here: within one step, the same placed features in the same step in two biomes cannot be in different orders. If two biomes both place ore_dirt and ore_gravel in UNDERGROUND_ORES, they must list them in the same relative order. Mismatched orders make the world fail to load. The safe habit: keep a consistent order for any features you reuse across biomes.

Under the Hood (skippable). Why a fixed order? Because features can build on each other: trees should generate after the ground is shaped, snow should fall after the trees exist. Fixing the step order makes that predictable across every biome in the world at once. These step names are also used by structure generation.

Walkthrough — Practice 1: a custom ore band in chosen biomes

Let’s build a placed feature that scatters a configured ore feature underground, but only in chosen biomes and only between Y=8 and Y=40. Rather than author the ore-type config from scratch here, we’ll point feature at a configured feature the game already ships and focus on the placement, which is the part we can author with confidence.

Create this file:

data/mypack/worldgen/placed_feature/frozen_ore.json

{
  "feature": "minecraft:ore_diamond",
  "placement": [
    {
      "type": "minecraft:count",
      "count": 8
    },
    {
      "type": "minecraft:in_square"
    },
    {
      "type": "minecraft:height_range",
      "height": {
        "type": "minecraft:uniform",
        "min_inclusive": { "absolute": 8 },
        "max_inclusive": { "absolute": 40 }
      }
    },
    {
      "type": "minecraft:biome"
    }
  ]
}

Read the placement list top to bottom and you can narrate exactly what happens: start with one attempt at the chunk corner → count 8 makes eight attempts → in_square scatters them randomly across the chunk → height_range drops each to a random Y between 8 and 40 (a uniform height provider using absolute vertical anchors) → biome keeps only the attempts whose biome actually lists this feature. Eight diamond-ore attempts per chunk, underground, but only where we allow it.

About the referenced ID. minecraft:ore_diamond is the configured feature we’re pointing at as a stand-in for “an ore blob.” Confirm the exact ID for your version on the official wiki. And once you know the ore-type config fields, you can replace this reference with your own configured feature in worldgen/configured_feature/ for a truly custom ore. The placement file above stays exactly the same either way.

Test it instantly with placefeature

Worldgen files live in dynamic registries, which (as you learned back in Chapter 9) do not reload with /reload. Changing a biome or feature normally means making a brand-new world (or restarting) to see it. That’s a slow feedback loop. There’s a shortcut for testing a feature on the spot: the placefeature command.

Put this in a function so you follow the book’s rule of writing commands in .mcfunction files, not the chat bar:

data/mypack/function/test_ore.mcfunction

placefeature minecraft:ore_diamond ~ ~-5 ~

The command is placefeature <feature> [<pos>]: it places the named configured feature at a position (here, 5 blocks below you). Two things to notice. First, placefeature takes a configured feature, not a placed feature; it tests “does this feature build correctly here?”, skipping the placement rules. Second, it has a few failure cases: it fails if there’s no configured feature with the provided ID, if the requirements for the selected feature are not met, or if the position isn’t loaded. So if nothing appears, check the ID first.

This lets you confirm the feature works right where you stand, then trust your placement list to handle the scattering once you reboot the world.

Wire it into a biome

Finally, reference the placed feature from a biome so it generates naturally. In a biome file from Chapter 42, the features field is the list of 11 steps; an ore goes in the UNDERGROUND_ORES step, which is the 7th slot (index 6). Here’s the features field with our placed feature dropped into that step (empty steps shown as empty lists so the ordering stays correct):

"features": [
  [],
  [],
  [],
  [],
  [],
  [],
  [ "mypack:frozen_ore" ],
  [],
  [],
  [],
  []
]

Each inner list is one decoration step; our mypack:frozen_ore placed feature sits in the UNDERGROUND_ORES step. Because the placed feature ends with the biome modifier, the ore now appears in this biome and stops at its borders. Save, make a new world (dynamic registries need a reboot, not /reload), and explore the biome’s caves.

Figure (to be captured). diamond ore generating in a band underground inside the custom biome, none visible in the neighbouring biome

Practice 2: a giant mushroom feature

Your second task is to scatter a giant-mushroom feature across a biome’s surface. The same split applies: the giant-mushroom shape is a configured feature (a feature type the game already knows how to build), and we author the placement. As before we reference an existing configured feature and put all our effort into the placement list, this time with a surface heightmap and a block-predicate filter so mushrooms only sprout on suitable ground.

data/mypack/worldgen/placed_feature/giant_mushroom.json

{
  "feature": "minecraft:huge_red_mushroom",
  "placement": [
    {
      "type": "minecraft:rarity_filter",
      "chance": 12
    },
    {
      "type": "minecraft:in_square"
    },
    {
      "type": "minecraft:heightmap",
      "heightmap": "WORLD_SURFACE_WG"
    },
    {
      "type": "minecraft:block_predicate_filter",
      "predicate": {
        "type": "minecraft:matching_blocks",
        "offset": [0, -1, 0],
        "blocks": "minecraft:mycelium"
      }
    },
    {
      "type": "minecraft:biome"
    }
  ]
}

Narrate it: rarity_filter 12 means most chunks get nothing and roughly one chunk in twelve gets a single attempt (mushrooms should be rare) → in_square scatters that attempt across the chunk → heightmap WORLD_SURFACE_WG lifts it to the ground surface instead of the world bottom → block_predicate_filter with a matching_blocks predicate checking the block below (offset [0,-1,0]) keeps only positions standing on minecraft:myceliumbiome keeps it inside the intended biome. A rare giant mushroom, only on mycelium, only in your biome.

Then add mypack:giant_mushroom to the VEGETAL_DECORATION step (the 10th slot, index 9) of a biome’s features list, since that’s the step for trees, bamboo, cacti, kelp, and other ground and ocean vegetation. Reboot into a new world to see it.

About the referenced ID and new mushroom shapes. minecraft:huge_red_mushroom stands in for “a giant-mushroom configured feature”; confirm the real ID for your version. Authoring a new mushroom or tree shape means writing a tree-type config (trunk provider, foliage provider, size, and so on). Open that feature type’s page on the wiki for those fields. The placement file is fully yours regardless.

Try It! Change huge_red_mushroom to a different surface configured feature, swap the predicate’s blocks to minecraft:grass_block, and bump chance down to 4 to make the feature common. You’re reusing the exact same placement skeleton. That’s the point of the what/where split.

What Can Go Wrong

  • Edited a feature or biome and /reload did nothing. Worldgen lives in dynamic registries, which don’t hot-reload. You must create a new world (or restart the game and re-enter) to pick up changes. Already-generated chunks keep their old generation regardless. Use placefeature to spot- check a configured feature without a reboot, but the placement and biome wiring only take effect in freshly generated chunks.
  • The world crashes when you reach the biome. Most often the biome placement modifier is sitting inside a placed feature that’s referenced from another configured feature. This isn’t caught at load, and crashes at generation time. Keep biome only in placed features that a biome points at directly.
  • The world won’t load at all after adding a feature to two biomes. Check the cross-biome ordering rule: any features shared between biomes in the same step must appear in the same relative order in every biome. Reorder them to match.
  • Nothing generates, no crash. Likely your feature ID is wrong (“no configured feature with the provided ID” is a placefeature failure case), or you put the placed feature in the wrong decoration step, or you forgot the biome modifier so the biome never claims it. Test the configured feature with placefeature first to isolate which half is broken.

What You Know Now

You can now describe the two-file split at the heart of Minecraft world decoration: the configured feature (the what, a hardcoded feature type plus a config) and the placed feature (the where, a configured feature wrapped in an ordered placement list). You can read and write a placement list, choosing placement modifiers that multiply attempts (count), spread and position them (in_square, height_range with a height provider and vertical anchors, heightmap), and filter them (rarity_filter, biome, block_predicate_filter with a block predicate). You know how a placed feature reaches the world through a biome’s features field and its eleven named decoration steps, and you can test a configured feature on the spot with placefeature instead of waiting for a reboot.

You can now build: a custom ore band gated to chosen biomes and heights, and a rare surface feature like a giant mushroom that only grows on the ground you choose, both by authoring the placement around an existing configured feature. The one piece you’ll look up rather than memorize is the inner config of each feature type; you know exactly where that edge is and how to find it on the wiki for your version. Next, in Chapter 44, you’ll stop decorating existing worlds and start building whole new ones: custom dimensions.

Chapter 44 — Custom Dimensions

What You’ll Build

Way back in Chapter 7, when we listed everything a data pack can configure, one item came with a promise attached: dimensions. You already know dimensions from playing: the Overworld you spawn in, the Nether you reach through a portal, the End where the dragon waits. Each one is its own separate space with its own sky, its own height, its own rules. Chapter 27 even let you reach across them with execute in. What Chapter 7 promised, and what this chapter delivers, is that a data pack can add a brand-new dimension of your own: a separate world that didn’t exist before you wrote a couple of JSON files.

By the end of this chapter you’ll have built a flat void arena in the mypack pack you started back in Chapter 9: a private, empty, perfectly flat dimension that’s good for a minigame. Players teleport in, the match happens on a clean slate, and nothing from the Overworld is in the way. You’ll do it with exactly two files: a dimension type (the physical rules of the space) and a dimension definition (which links that type to a generator that decides what blocks fill the world). Then you’ll travel into it with execute in, and as practice you’ll build a second dimension made of thin floating slabs: a sky world.

Figure (to be captured). a player standing in an empty flat void dimension, flat ground stretching out under a plain sky

Heads up before you start — this dimension needs a reboot, not /reload. Dimensions live in a dynamic registry (Chapter 7’s word for a registry data packs are allowed to add to). You learned the rule for these back with biomes in Chapter 42: the game only reads dynamic-registry files when a world loads. So after you write these files, /reload will not make the new dimension appear. You have to leave the world and open it again. We’ll repeat this at the moment it matters, but keep it in mind so you’re not confused when /reload seems to do nothing.

What a dimension really is

Here’s the plain definition:

Dimensions are parallel worlds within a Minecraft world characterized by a way of generation, biomes and structures, and other things unique to one dimension. Each dimension is its own separate 3D space, not affecting other dimensions.”

That last sentence is the key idea for this chapter. A dimension is a separate 3D space, a whole independent block world. Blocks you place in the Nether don’t show up in the Overworld; they’re different spaces that happen to belong to the same saved world. The Overworld, the Nether, and the End are just the three dimensions the game ships with.

And here’s the promise being kept. As the wiki puts it, under “Custom dimensions”:

“Using data packs, it is possible to create custom dimensions and dimension types that have different biomes, world generation and properties from the regular dimensions. (Java Edition only)”

So a data pack can hand the game a new separate space, with its own biomes, its own way of generating terrain, and its own properties like height and lighting. That “(Java Edition only)” note matters: everything in this chapter is a Java Edition feature.

The two files: type and definition

Making a dimension takes two JSON files, and it’s worth being clear from the start about which does what, because the names are similar:

  • A dimension type describes the physical rules of a space: how tall it is, whether it has a sky, how bright the ambient light is, whether time is frozen. Dimension types “define properties of a dimension such as world height build limits, the ambient light, and more.” These files live in the folder data/<namespace>/dimension_type (note the singular folder name), the same rule you’ve followed since Chapter 9.

  • A dimension definition (or just “dimension file”) is the file that creates an actual dimension by pointing at a type and attaching a generator. Dimensions are stored as JSON files within a data pack, at the path data/<namespace>/dimension/<name>.json. Singular folder again: dimension.

Think of it like a recipe and a finished dish. The dimension type is a reusable description of what kind of space this is; the dimension definition is the one specific world that uses it and decides what’s in it. You can have one type and reuse it for several dimensions, the same way the game’s overworld type is shared.

We’ll write the type first, because the definition refers to it.

The dimension type file

A dimension type is a single JSON object. Here is the whole list of fields it can hold, so you know what each one means before we write the file:

  • coordinate_scale (number) — “The multiplier applied to coordinates when leaving the dimension.” The Nether’s is 8, which is why one Nether block equals eight Overworld blocks (you met that 8:1 ratio in Chapter 27). Must be between 0.00001 and 30000000.0.
  • has_skylight (true/false) — “Whether the dimension has skylight or not. If set to false, weather is additionally disabled.”
  • has_ceiling (true/false) — “Whether the dimension has a bedrock ceiling,” the way the Nether does. This changes how respawn and mob spawning are calculated.
  • has_ender_dragon_fight (true/false) — “Whether this dimension can have an ender dragon fight.” Leave this false unless you want End-style dragon machinery.
  • ambient_light (number) — “How much light the dimension has. When set to 0, it completely follows the light level; when set to 1, there is no ambient lighting.” In plain terms: 0.0 means it gets properly dark at night like the Overworld; higher values keep a baseline glow everywhere.
  • has_fixed_time (true/false, optional, defaults to false) — “Whether this dimension has fixed time.” With this on, the sun never moves.
  • monster_spawn_block_light_limit (whole number 0–15) — “Block light level must be less than or equal to this value for monsters to spawn.”
  • monster_spawn_light_level (0–15, or an int provider) — sets the spawn-light formula; a plain whole number is the simple form.
  • logical_height (whole number) — “The maximum height to which chorus fruits and Nether portals can bring players within this dimension.” Can’t be greater than height.
  • min_y (whole number) — “The minimum height in which blocks can exist within this dimension. Must be between -2032 and 2031 and be a multiple of 16.”
  • height (whole number) — “The total height in which blocks can exist within this dimension. Must be between 16 and 4064 and be a multiple of 16.” (So the highest place you can put a block is min_y + height - 1.)
  • infiniburn (string) — “A block tag with #. Fires on these blocks burns infinitely.” The Overworld uses the block tag #minecraft:infiniburn_overworld.
  • skybox (string, optional, defaults to overworld) — “The skybox to use. Can be none, overworld, or end.”
  • cardinal_light (string, optional, defaults to default) — “Direction of cardinal light affecting blocks. Can be default or nether.”
  • default_clock (string, optional) — which world clock the /time command uses here.

Under the Hood — min_y and height are about block space, not difficulty (skippable). These two are the most important fields to get right, and they trip people up because they interact. min_y is the floor (the lowest Y a block can exist at), and height is how many blocks of vertical room there are above that floor. The Overworld uses min_y of -64 and height of 384, so blocks can exist from Y=-64 up to Y=319. Both numbers must be multiples of 16: pick min_y and height from the 16-times-table (…-64, -48, -32, -16, 0, 16, 32…) or the game will reject the file.

For the arena we want a small, well-behaved space: a normal sky so players can see, light that gets dark normally, no ceiling, and a modest height range. Here’s the file. Every field name is copied from the list above.

data/mypack/dimension_type/arena.json

{
  "ultrawarm": false,
  "natural": true,
  "coordinate_scale": 1.0,
  "has_skylight": true,
  "has_ceiling": false,
  "has_ender_dragon_fight": false,
  "ambient_light": 0.0,
  "piglin_safe": false,
  "bed_works": true,
  "respawn_anchor_works": false,
  "has_raids": true,
  "logical_height": 256,
  "infiniburn": "#minecraft:infiniburn_overworld",
  "effects": "minecraft:overworld",
  "min_y": -64,
  "height": 320,
  "monster_spawn_block_light_limit": 0,
  "monster_spawn_light_level": 7
}

Where these values come from. The field list above covers the definitions (coordinate_scale, has_skylight, has_ceiling, has_ender_dragon_fight, ambient_light, has_fixed_time, the two monster_spawn_* fields, logical_height, min_y, height, infiniburn, skybox, cardinal_light, and default_clock). On top of those, each of the three vanilla dimensions (and the Overworld Caves preset) ships with a concrete set of values, and that’s where the extra fields above come from: ultrawarm, natural, piglin_safe, respawn_anchor_works, bed_works, and has_raids, each true or false per dimension, plus the Overworld’s infiniburn tag (infiniburn_overworld) and its effects (minecraft:overworld). Every value in the file above is simply the Overworld’s own settings, written out as JSON: its ambient_light is 0.0, its logical_height is 256, and its coordinate_scale is 1.0. Tweak height/min_y to taste within the limits below.

Read down the values: a 1:1 coordinate scale (no Nether-style scaling), a real sky (has_skylight true), no ceiling, light that fully follows the normal day/night cycle (ambient_light 0.0), a build range from Y=-64 up to Y=255 (min_y -64 plus height 320, both multiples of 16), and an infiniburn block tag (the value shown is the Overworld’s). The two monster_spawn fields are set low so the arena stays calm. That’s a complete, sky-lit, flat-friendly space.

The dimension definition file

Now the file that actually makes the dimension exist. The root object has just two parts:

type: dimension type. Can be preset overworld, the_nether, the_end, overworld_caves, or a custom dimension type”, and “generator: Generation settings used for that dimension.”

So type is the identifier of a dimension type (either a built-in one or the custom one you just wrote), and generator is a nested object describing how to fill the world with blocks. The generator has its own type, which is “One of noise, flat, or debug”. Here is what each one does:

  • noise — “The generator used in all the default dimensions.” This builds real, varied terrain from noise. It’s how the Overworld and Nether are made. It’s complicated, and we’ll describe its shape later in this chapter but save the deep machinery for Chapter 45.
  • flat — “The generator type used for superflat worlds.” Flat layers of your choosing. This is what we’ll use for the arena, because a void is just the simplest possible flat world.
  • debug — a special grid-of-every-block generator with “no additional fields”; not something you’d ship in a real pack.

We want flat. The flat generator takes one extra field, settings, holding the “Superflat settings.” So what do superflat settings look like? Here’s a complete, real example (it’s the server-config form, but the JSON shape is the same one the generator uses):

{"biome":"minecraft:plains","layers":[{"block":"minecraft:bedrock","height":1},{"block":"minecraft:dirt","height":2},{"block":"minecraft:grass_block","height":1}]}

That tells us the three field names we need: a biome for the whole world, and a layers list where each layer is an object with a block and a height. Here’s what they mean: layers is “interpreted from top to bottom, starting at world bottom” (so the first layer is the lowest), each layer’s height is “the height of this layer,” and block is “the block ID.”

A void is the simplest layer list there is. The Superflat page lists a built-in preset called “The Void” whose entire contents are: “Air x1.” So a void dimension is a flat world whose only layer is a single layer of air. Here’s the arena:

data/mypack/dimension/arena.json

{
  "type": "mypack:arena",
  "generator": {
    "type": "minecraft:flat",
    "settings": {
      "biome": "minecraft:the_void",
      "layers": [
        {
          "block": "minecraft:air",
          "height": 1
        }
      ]
    }
  }
}

Look at how the two files connect. The definition’s "type": "mypack:arena" is the identifier of the dimension type file you wrote a moment ago: data/mypack/dimension_type/arena.json becomes the ID mypack:arena, exactly the namespace-and-path naming you’ve used since Chapter 8. The generator is minecraft:flat, and its settings say: one layer, one block tall, made of air, over the minecraft:the_void biome. That’s an empty world. The file itself sits at data/mypack/dimension/arena.json, so the new dimension’s own identifier is mypack:arena too: a dimension file’s ID and the dimension it makes share a name.

Try It! — give the arena a floor. An all-air void means players fall forever, which is great if your minigame teleports them to a built platform but awkward otherwise. To give the whole dimension a solid floor, change the layers list to two layers (bedrock then air), like the void preset with a base:

"layers": [
  { "block": "minecraft:bedrock", "height": 1 },
  { "block": "minecraft:air", "height": 64 }
]

Remember layers go bottom-up, so the bedrock is the floor and the 64 layers of air sit on top of it.

Loading it: reboot, then travel in

This is the moment the warning from the start of the chapter pays off. You’ve written both files. Your instinct, after every chapter so far, is to run /reload. Don’t expect it to work here. As you learned with biomes in Chapter 42, dimensions are a dynamic registry, and the rule for those is that the game reads them only when a world loads. So:

  1. Save both files.
  2. Quit to the title screen (leave the world entirely).
  3. Open the world again.

Now mypack:arena exists. To go there, you use the command Chapter 27 introduced: execute in <dimension>. Back then you used it to reach the Nether; it works for any loaded dimension, including yours. We’ll wrap it in a function so it’s easy to run again.

data/mypack/function/goto_arena.mcfunction

execute in mypack:arena run tp @s 0 65 0

Running this function teleports you into the mypack:arena dimension and drops you at coordinates (0, 65, 0). (If you took the “give the arena a floor” Try It, you’ll land on or above the bedrock; if you kept the pure void, you’ll need something to stand on; see “What Can Go Wrong.”) That single line is the whole journey: execute in switches the execution dimension to yours, and run tp teleports you there. To get back, point another function at minecraft:overworld the same way.

Figure (to be captured). the chat showing the goto_arena function running, and the player now standing in the new dimension

Noise generators and biome sources

The arena used the flat generator because a void is the cleanest possible example. But the real worlds (anything with hills, caves, oceans, ores) use the noise generator. You won’t master noise generation until Chapter 45, but you should understand the shape of a noise dimension now, because it’s the other half of how dimensions work.

The noise generator takes two extra fields:

  • settings — “Settings for the noise generator.” This is either the identifier of a noise settings file or an inline settings object. Noise settings are “for generating the shape of the terrain and noise caves, and what blocks the terrain is generated with,” stored at data/<namespace>/worldgen/noise_settings. There are ready-made vanilla ones you can point at by ID: minecraft:overworld, minecraft:amplified, minecraft:nether, minecraft:caves, minecraft:end, and minecraft:floating_islands.
  • biome_source — “Settings determining the biome layout.” This object has its own type choosing how biomes are arranged.

The biome-source type values are:

  • fixed — “uses one specified biome everywhere.” Its one extra field is biome: the single biome to use.
  • multi_noise — spreads many biomes across the world using climate noise. It takes either a preset (“The default parameter lists are overworld and nether”) or an explicit biomes list of parameter points.
  • checkerboard — “places biomes in a checkerboard pattern,” from a biomes list and an optional scale.
  • the_end — “The biome source used for the End dimension. This biome source has no additional fields.”

So the simplest custom terrain dimension you could build is a noise generator pointed at a vanilla noise settings preset, with a fixed biome source. Here’s the structure:

data/mypack/dimension/canyon.json

{
  "type": "minecraft:overworld",
  "generator": {
    "type": "minecraft:noise",
    "settings": "minecraft:overworld",
    "biome_source": {
      "type": "minecraft:fixed",
      "biome": "minecraft:desert"
    }
  }
}

This makes a dimension that generates with the Overworld’s terrain shape but is desert everywhere, using the built-in minecraft:overworld dimension type. Notice we reused a vanilla dimension type (minecraft:overworld) here instead of writing one. Both files are independent, so you can mix and match.

The inside of a noise settings file is Chapter 45. A noise settings file contains a “noise router” (a collection of density functions for terrain, biome layout, aquifers, and ore veins) and a “surface rule” (which blocks make the surface). Writing your own noise settings (density functions, the router, surface rules, carvers) is exactly what Chapter 45 covers, so we’ll save the internals for there. For this chapter, always point settings at one of the vanilla preset IDs above; don’t hand-write the object.

Practice

1. A sky dimension of floating slabs. Build a second dimension, mypack:sky, that’s a thin floating platform high in an otherwise empty world: the start of a sky-island minigame. Make a dimension type for it (you can copy arena.json and rename it), then write the dimension file with a flat generator whose layers stack a few solid blocks on top of empty space below. Because flat layers start at the world bottom and the lowest layer fills first, put your solid blocks as the first layers and leave it at that. Everything above the last layer is open sky:

data/mypack/dimension/sky.json

{
  "type": "mypack:sky",
  "generator": {
    "type": "minecraft:flat",
    "settings": {
      "biome": "minecraft:plains",
      "layers": [
        {
          "block": "minecraft:stone",
          "height": 3
        },
        {
          "block": "minecraft:grass_block",
          "height": 1
        }
      ]
    }
  }
}

That’s a four-block-thick slab (three stone, one grass) over the plains biome, with open air above it and the void below. Don’t forget the matching data/mypack/dimension_type/sky.json, and reboot the world before you execute in mypack:sky run tp @s 0 100 0 to visit it.

2. A teleport pair. Write two functions, goto_arena (you already have it) and goto_overworld, each using execute in <dimension> run tp so a player can hop into the arena and back out. Hook them up however you like: a Chapter 34 trigger, a dialog button from Chapter 39, or just run them by hand.

3. (Try It, harder) A fixed-biome noise world. Using the canyon.json structure above as a model, make a noise dimension that generates with minecraft:floating_islands settings and a fixed biome source set to minecraft:the_end: a custom End-island world reachable from the Overworld. Point its type at minecraft:the_end so it gets End-style physics.

What Can Go Wrong

/reload did nothing, and the dimension isn’t there. This is the single most common surprise. Dimensions are a dynamic registry; the game reads them only when the world loads. /reload rebuilds functions and recipes, but not dimensions. The fix is always the same: quit to the title screen and reopen the world. If you edit a dimension or dimension-type file later, you reboot again. There’s no /reload shortcut for these.

The world won’t load, or the dimension is missing after a reboot. Almost always a bad value in the dimension type. The usual culprits: min_y or height that isn’t a multiple of 16, a height outside the 16–4064 range, or a logical_height larger than height. Re-check those three numbers against the rules in the field list. A misspelled field name or a missing comma will do it too, the same JSON-shape mistakes from Chapter 8.

I teleported in and fell forever. If you kept the pure-air void (one layer of air, no floor) and teleported a player in with nothing to stand on, they’ll drop through the empty world. Either give the dimension a floor (the “give the arena a floor” Try It), build a platform with /setblock or /fill before sending players in, or teleport them onto a structure you’ve placed. A void is meant to be empty; it’s your job to put something under the players.

Modern Minecraft — you don’t need a mod for this. Older tutorials treat custom dimensions as a Forge/Fabric-only feature, because for years they were. Since the world-generation data-pack system landed, a plain data pack can add dimensions with no mod loader at all: just the two JSON files you wrote in this chapter. If a guide tells you to install a mod to make a new dimension, it’s out of date for current Java Edition.

Chapter 45 — Noise, Density Functions, and Surface Rules

The hardest chapter in this book. It closes Part XI, and it is genuinely advanced: the deepest corner of world generation. If a section makes your head spin, that is normal. Read it once for the shape of the idea, build the example, and come back later. You do not need to memorize every field. You need to understand the pipeline and be able to copy-and-adjust the worked files. We will favor understanding over covering everything, and we’ll point you onward whenever a topic (like the raw noise math) is a deep specialty of its own.

What You’ll Build

In Chapter 44 you built a custom dimension (a separate world space) and pointed its minecraft:noise generator at a noise settings file. You treated that file as a black box: it decided what the land looked like, and you didn’t open it. This chapter opens it.

By the end you’ll have, inside the mypack data pack you started in Chapter 9:

  • a noise settings file that makes exaggerated terrain: taller mountains and deeper valleys than the normal Overworld;
  • a surface rule that paints the ground with red sand over terracotta, like a desert mesa;
  • a carver that digs big caves.

Along the way you’ll meet density functions (the math that decides where land is), the noise router (the bundle of density functions a dimension uses), surface rules (the decision tree that paints the surface blocks), and carvers (caves and ravines). We’ll test everything in a fresh world using the dimension you built in Chapter 44.

Figure (to be captured). a custom dimension showing exaggerated terrain — tall jagged mountains and deep valleys — with red-sand-and-terracotta ground

A word before we start

World generation is the most complex part of data packs, and density functions are the most complex part of world generation. Each field has a name and a job, but the deep math behind several of them (exactly how a noise turns into terrain, how splines bend the land) is a specialty in its own right. When we reach one of those, this chapter will tell you plainly that it’s a topic of its own and point you to where the full spec lives, rather than hand-wave a half-formula. The good news: you can build real, working terrain with just a handful of the pieces, and that’s exactly what we’ll do.

The big picture: how terrain gets decided

Before any single field, hold the whole pipeline in your head. When Minecraft generates a chunk in your dimension, four things happen in order:

  1. Density functions run. A density function is a little math expression that takes a position in the world (x, y, z) and returns a single number. It “makes up mathematical expressions to obtain a number from a position.”
  2. The noise router collects those density functions into named slots. One slot, final_density, decides the basic shape: where the number is positive, the spot becomes solid block; where it’s negative, it becomes air (or water). final_density “determines where there is an air or a default block.”
  3. The surface rule runs over that solid shape and decides which block goes on top: grass, sand, terracotta bands, deepslate, bedrock. Surface rules “determine the block for each solid position of the terrain.”
  4. Carvers dig caves and ravines out of the result.

All four live inside, or are pointed to by, the noise settings file. So the noise settings file is our home base for this chapter. Here is where it lives:

Noise settings are for generating the shape of the terrain and noise caves, and what blocks the terrain is generated with, stored as JSON files within a data pack in the path data/<namespace>/worldgen/noise_settings, and are used with the minecraft:noise generator in a dimension.”

That last clause is the bridge from Chapter 44: the dimension’s minecraft:noise generator names a settings, and that is one of these files. Vanilla ships several you’ve seen the names of: minecraft:overworld, minecraft:amplified, minecraft:nether, minecraft:caves, minecraft:end, and minecraft:floating_islands, and we’re about to write our own.

Density functions: a number from a position

A density function is a JSON file (or a piece of JSON nested inside another file) that describes a math expression. It lives at data/<namespace>/worldgen/density_function/, and it “can be a constant number or an object.”

The simplest density function is just a number. This is the constant shorthand:

0.5

That’s a complete, valid density function: every position gets the value 0.5. The longer way to write the same thing names the type:

data/mypack/worldgen/density_function/half.json

{
  "type": "minecraft:constant",
  "argument": 0.5
}

Every density-function object has a "type" field naming what kind of math it does, plus a few extra fields that depend on the type. The type is “the ID of the density function type,” and the “other additional fields depend on the value of type.” The constant type takes one field, argument.

You will rarely need a separate file for each tiny expression. You can nest one density function directly inside another wherever a density function is expected. We’ll do almost everything inline.

Under the Hood — there are a LOT of density-function types (skippable)

There are well over thirty density-function types. Most of them are for the game’s internal use: types like cache_2d, flat_cache, cache_once, blend_density, beardifier, old_blended_noise, and end_islands exist to make vanilla generation fast or to blend with chunks from older versions, and several of them “should not be referenced in data packs.” You do not need them. This chapter teaches the small set you actually combine by hand: the arithmetic ones, noise, and y_clamped_gradient. If you ever go spelunking in the vanilla files and see an unfamiliar type, that’s fine: leave it alone.

The arithmetic types: combining numbers

Most hand-built terrain is just a few simple types glued together. These take other density functions as inputs and combine them. Each name below is the exact type string, with its one-line description:

typeWhat it doesInputs
constant“A constant value.”argument (a number)
add“Adds two density functions together.”argument1, argument2
mul“Multiplies two inputs.”argument1, argument2
min“Returns the minimum of two inputs.”argument1, argument2
max“Returns the maximum of two inputs.”argument1, argument2
abs“Calculates the absolute value of the input.”argument
clamp“Clamps the input between two values.”input, min, max

So add of two functions gives their sum at every position, mul gives their product, min/max pick the smaller/larger of the two, abs strips the minus sign, and clamp forces the result to stay between a floor and a ceiling. That’s ordinary arithmetic. The only twist is that the “numbers” are themselves functions of position.

One detail worth flagging for clamp: its input must be a direct density function written out in place, not a reference to a density-function ID. There’s a known bug here: “Clamp density function takes a direct input and doesn’t allow a reference.” Good to know if you ever get a mysterious error on a clamp.

Getting variety: the noise type

Pure arithmetic gives you smooth, boring shapes. Real terrain wiggles, and the wiggle comes from noise. The noise density-function type samples a noise pattern. Here’s the type:

noise — Samples a noise.” Fields: type (= noise), noise (the noise to sample), xz_scale (“Scales the X and Z before sampling”), y_scale (“Scales the Y before sampling”).

A few cousins exist for special cases: shifted_noise (“Similar to noise, but first shifts the input coordinates”) and interpolated (“Interpolates at each block in one cell based on the input density function value of some cells around”). We’ll use plain noise.

What does noise point at? A separate noise file:

“A noise is a technical JSON file that can be referenced by a density function and surface rule. They are stored within a data pack in the folder data/<namespace>/worldgen/noise.”

A noise file has two fields: firstOctave and amplitudes, the “First octave” and a list of “Amplitudes of sub-noise.” Here is a small one we’ll use:

data/mypack/worldgen/noise/rolling.json

{
  "firstOctave": -7,
  "amplitudes": [1.0, 1.0, 1.0, 1.0]
}

Designing noise from scratch is a topic of its own. Exactly how firstOctave and the amplitudes list translate into the size and roughness of the bumps is a dense formula involving octaves, Perlin noise, and a normalizing factor. There’s no simple “use these numbers for hills this big” dial. When you want to design noise precisely, the wiki’s Noise page is the place to go for the full math. For now, treat the values above as a knob to experiment with: they’re a reasonable starting point in the spirit of vanilla noises. Smaller (more negative) firstOctave and more list entries generally mean a more detailed pattern.

One thing you can rely on: there are hard-coded noises with fixed jobs. For example, minecraft:surface “affects the surface layer thickness in surface rules” and minecraft:clay_bands_offset is “used to generate badland terracotta bands.” You don’t write those; the game already has them.

Shaping height: y_clamped_gradient

The one density function that turns “a number per position” into “a world with a sky and a floor” is y_clamped_gradient:

y_clamped_gradient — Clamps the Y coordinate between from_y and to_y and then linearly maps it to a range.” Fields: from_y, to_y, from_value, to_value.

In plain terms: it makes the density depend on height. Here is the exact example of a flat world built this way:

“Using the y_clamped_gradient density function, a flat world can be created. In the following example positions at Y=-64 get a density of 1 and positions at Y=320 get a density of -1.”

{
  "type": "minecraft:y_clamped_gradient",
  "from_y": -64,
  "to_y": 320,
  "from_value": 1,
  "to_value": -1
}

Read that as: at the bottom (from_y = -64) the density is +1 (solid), and at the top (to_y = 320) it’s -1 (air), with a smooth slope in between. Because solid means “positive,” this fills the bottom of the world and leaves the top empty: flat ground with a flat sky. The number crosses zero somewhere in the middle, and that height is your terrain surface.

To turn that flat slab into hills, you add a noise to it:

“By adding the previous y_clamped_gradient to a noise, the height of the terrain is based on a noise that varies along the X and Z coordinates.”

{
  "type": "minecraft:add",
  "argument1": {
    "type": "minecraft:y_clamped_gradient",
    "from_y": -64,
    "to_y": 320,
    "from_value": 1,
    "to_value": -1
  },
  "argument2": {
    "type": "minecraft:noise",
    "noise": "minecraft:gravel",
    "xz_scale": 2,
    "y_scale": 0
  }
}

Now the surface “wobbles”: where the noise is positive it pushes the zero-crossing higher (a hill), where it’s negative it pushes it lower (a valley). Two tuning notes you can lean on: “xz_scale: 0.5 makes the terrain smoother,” and to get overhangs “the noise also needs to vary along the Y coordinate. This can be done with xz_scale: 1 and y_scale: 1” (because y_scale: 0 means the noise ignores height, so the wobble is the same all the way up a column).

That little pattern, a height gradient plus a noise, is the heart of nearly all custom terrain. Everything fancier is variations on it.

The noise router: where the density functions plug in

A single final_density function is the star, but a dimension needs a whole bundle of density functions for different jobs. That bundle is the noise router:

“The noise router is a collection of density functions… used for terrain generation, biome layout, aquifers, ore veins, and more. A noise router is a part of a dimension’s noise settings.”

Here are the router’s fields. You will set only a couple of them by hand; the rest you can leave at 0 for a simple custom dimension.

Router fieldWhat it controls
final_density“Determines where there is an air or a default block. If positive, returns a default block… Otherwise, an air block.” The terrain shape.
preliminary_surface_level“A 2D density function… determining the Y-level of the preliminary surface… Used by the generation of aquifers and surface rules.”
temperature“The temperature values only for biome placement.”
vegetation“The humidity values only for biome placement.”
continents“The continentalness values only for biome placement.”
erosion“The erosion values only for biome placement and aquifer generation.”
depth“The depth values only for biome placement and aquifer generation.”
ridges“The weirdness values only for biome placement.”
barrierAquifer separation in caves.
fluid_level_floodednessProbability of liquid in a cave aquifer.
fluid_level_spreadHeight of the liquid surface in aquifers.
lava“Affects whether an aquifer here uses lava instead of water.”
vein_toggle“Affects ore vein type, vertical range and richness.”
vein_ridged“Controls which blocks are part of a vein.”
vein_gap“Affects which blocks in a vein are ore blocks.”

One thing is crystal clear, and it’s worth stating plainly because it’s the key to not getting lost: the biome-parameter fields (temperature, vegetation, continents, erosion, depth, ridges) “do not affect terrain shape, as terrain generation is defined in final_density.” In other words, for a simple dimension where you just want a shape, you only have to fill in final_density. The rest can be 0.

Modern Minecraft — continents and ridges, not “continentalness” and “weirdness”

If you read about terrain online you’ll hear the words continentalness and weirdness: the hidden parameters that the vanilla Overworld uses to lay out biomes. In the noise-router file, though, those fields are named continents (“the continentalness values”) and ridges (“the weirdness values”). So the field you’d type is continents, even though everyone talks about “continentalness.” Type the real field names: continents / ridges.

Two values worth memorizing. “Setting the final density… to 0 results in a void dimension, similarly setting it to 1 would completely fill the world with stone.” That’s your sanity check: final_density: 0 → empty world; final_density: 1 → solid stone world.

The final_density is also where the vein fields plug into the bigger machine. The three vein_* fields control ore vein behavior (which ore at which depth, and the exact thresholds), and the published rules describe how the vanilla veins are wired rather than a step-by-step recipe for designing your own. Custom ore veins are their own deep specialty, well beyond what one terrain chapter needs. For this chapter we leave the three fields at 0 (and set ore_veins_enabled: false) and treat custom veins as out of scope; when you want to build veins, start from a vanilla noise settings file and adapt its vein_* functions.

Under the Hood — splines (skippable)

The vanilla Overworld goes well beyond adding a single noise to a single gradient: it bends the terrain through splines, smooth curves that map one value (say, how far inland you are) to another (say, how high the land sits). There’s a spline density-function type for this: it “Computes a cubic spline,” taking a coordinate (the input density function) and a list of points, each with a location, a value, and a derivative (“The slope at this point”). You can build custom terrain profiles this way. Designing good splines (choosing the points and slopes so the land flows naturally) is a craft of its own; when you want full control, the wiki’s density-function page is where the spline grammar lives. For this chapter, just know splines exist for fine control: our exaggerated terrain reaches its drama with mul, which is plenty.

Walkthrough: a noise settings for exaggerated terrain

Time to assemble a real file. We’ll make a final_density that’s the usual gradient-plus-noise, but we’ll multiply the noise by a constant to exaggerate it: bigger bumps mean taller mountains and deeper valleys.

First, the noise file from earlier (if you haven’t made it yet):

data/mypack/worldgen/noise/rolling.json

{
  "firstOctave": -7,
  "amplitudes": [1.0, 1.0, 1.0, 1.0]
}

Now the noise settings. This is a complete file, nothing elided. Every field name and the default-block shape follow the standard noise-settings skeleton; we fill in a dramatic final_density.

data/mypack/worldgen/noise_settings/exaggerated.json

{
  "sea_level": 63,
  "disable_mob_generation": false,
  "aquifers_enabled": false,
  "ore_veins_enabled": false,
  "legacy_random_source": false,
  "default_block": {
    "Name": "minecraft:stone"
  },
  "default_fluid": {
    "Name": "minecraft:water",
    "Properties": {
      "level": "0"
    }
  },
  "noise": {
    "min_y": -64,
    "height": 384,
    "size_horizontal": 2,
    "size_vertical": 2
  },
  "noise_router": {
    "barrier": 0,
    "fluid_level_floodedness": 0,
    "fluid_level_spread": 0,
    "lava": 0,
    "temperature": 0,
    "vegetation": 0,
    "continents": 0,
    "erosion": 0,
    "depth": 0,
    "ridges": 0,
    "preliminary_surface_level": 0,
    "initial_density_without_jaggedness": 0,
    "final_density": {
      "type": "minecraft:add",
      "argument1": {
        "type": "minecraft:y_clamped_gradient",
        "from_y": -64,
        "to_y": 320,
        "from_value": 1,
        "to_value": -1
      },
      "argument2": {
        "type": "minecraft:mul",
        "argument1": 3.0,
        "argument2": {
          "type": "minecraft:noise",
          "noise": "mypack:rolling",
          "xz_scale": 1,
          "y_scale": 0
        }
      }
    },
    "vein_toggle": 0,
    "vein_ridged": 0,
    "vein_gap": 0
  },
  "spawn_target": [],
  "surface_rule": {
    "type": "minecraft:block",
    "result_state": {
      "Name": "minecraft:stone"
    }
  }
}

Walk the important parts:

  • noise block. Here min_y is “The minimum Y coordinate where terrain starts generating… Must be divisible by 16,” height is “The total height where terrain generates… Must be divisible by 16,” and size_horizontal / size_vertical are each a “Value between 0 and 4.” Our -64 and 384 are the vanilla Overworld values, both divisible by 16.
  • final_density. This is the whole point. Inside the add, argument1 is the height gradient (solid at the bottom, air at the top); argument2 is our rolling noise multiplied by 3.0 using mul. Tripling the noise triples how far the surface swings up and down: that’s the exaggeration. Turn the 3.0 up for even crazier terrain, down toward 1.0 for gentle hills.
  • default_block / default_fluid. These are the block “used for the terrain” and the one “used for seas and lakes.” Wherever final_density is positive you get stone; below sea_level, the air gets filled with water.
  • surface_rule. For now it’s the simplest possible rule: a single block rule painting everything minecraft:stone. We replace this next.
  • spawn_target: []. This is “A list of climate parameters” for choosing the spawn point; an empty list is allowed (“Required, but can be empty”).

To use this, point your Chapter 44 dimension at it. The minecraft:noise generator’s settings becomes mypack:exaggerated:

data/mypack/dimension/exaggerated_world.json (from Chapter 44, shown for the cross-reference, not new)

{
  "type": "minecraft:overworld",
  "generator": {
    "type": "minecraft:noise",
    "settings": "mypack:exaggerated",
    "biome_source": {
      "type": "minecraft:fixed",
      "biome": "minecraft:plains"
    }
  }
}

Create a fresh world with the pack, run execute in mypack:exaggerated run tp @s ~ ~ ~ from a function (Chapter 44’s technique), and you should drop into wildly tall, jagged land.

Figure (to be captured). the exaggerated dimension — towering stone spikes and deep gorges next to normal-scale terrain for comparison

Surface rules: painting the surface

Right now everything is bare stone. The job of deciding which block shows on the surface belongs to the surface rule:

Surface rules are used to determine the block for each solid position of the terrain. They are responsible for grass and dirt layers, creating different bands of terracotta in badlands, for deepslate, bedrock, and more.”

A surface rule is a decision tree: “using a combination of sequences and conditions, it can implement checks to place the right blocks in the right places.” Like every other JSON object here, each rule has a type. There are four rule types:

Rule typeWhat it doesFields
block“Places a specified block.”result_state (the block state)
sequence“Tries surface rules in order, only the first that matches is applied.”sequence (a list of rules)
condition“Checks a condition.”if_true (a condition), then_run (a rule)
badlands“Used in badlands to place terracotta. This rule has no extra fields.”

(Watch out for a typo on the wiki here: the type is written as bandlands with a {{sic}} marker flagging it as a known misspelling. The real type name is almost certainly badlands; confirm it in-game before relying on it. We don’t use it in this chapter anyway.)

Read those four together and the pattern clicks: a sequence is a list it tries top to bottom, stopping at the first match; a condition says “if this is true, run that rule”; and a block is the leaf that actually places something. So a typical surface rule reads like: “In order, if you’re near the surface, place sand; otherwise, place stone.”

The conditions are the interesting part, because they’re how you ask where am I? Every condition also has a type. Here are the available ones, with their exact names:

Condition typeWhat it checks
biome“Checks the biome at the current position.” Field: biome_is (a list of biome IDs).
noise_threshold“Computes the noise value… checks if it is between the min and max threshold.” Fields: noise, min_threshold, max_threshold.
y_above“Checks if the current position is above a specified height (exclusive).” Field: anchor.
water“Checks if the current position is above water, based on terrain depth.” Fields: offset, surface_depth_multiplier, add_stone_depth.
stone_depth“Checks if the current position is within a specified distance from the surface.” Fields: surface_type (floor/ceiling), offset, add_surface_depth.
steep“Checks if the current position is a steep face on the north or east sides of a mountain.” (no extra fields)
vertical_gradient“Compares the current Y position, with a messy transition” — like the deepslate/bedrock fade.
above_preliminary_surface“Checks if the current position is above the preliminary surface level.” (no extra fields)
hole“Passes for columns where the surface depth is 0.” (no extra fields)
temperature“Checks if the current block is in a biome that is cold enough for snowfall.” (no extra fields)
not“Inverts a surface condition.” Field: invert (the condition to flip).

The two you reach for most when painting a surface band are stone_depth and y_above:

  • stone_depth is “how far am I from the surface?” With surface_type: floor, “the blocks will be placed based on the distance to the surface above.” Its offset is how thick a layer you want.
  • y_above is “am I above this height?” Its anchor is a vertical anchor (the same Y-anchor format you’d have met building dimensions). There’s also a water condition for beaches and an above_preliminary_surface condition that vanilla uses “to prevent grass blocks from being placed in noise caves.”

A note on the deeper machinery. Several conditions lean on internal quantities that live in formula form. stone_depth and water use a “terrain depth” / “surface depth” computed with expressions like floor(surface(X,0,Z) × 2.75 + 3.0 + …), and the anchor / vertical_anchor sub-formats are their own small specs. Tuning those precisely is a topic of its own; when you need the full surface-depth math or the exact vertical-anchor grammar, the wiki’s surface-rule and vertical-anchor pages are where they live. We’ll stick to the simplest forms shown in the worked examples below, and call out where a value is a knob to experiment with.

Walkthrough: red sand over terracotta

Let’s paint a desert-mesa surface: a thin cap of red sand on top, terracotta underneath. We’ll replace the surface_rule in exaggerated.json with a sequence that, in order:

  1. If we’re within a few blocks of the surface (a stone_depth floor check), place red_sand.
  2. Otherwise, place terracotta.

Because a sequence stops at the first match, putting the thin red-sand rule first and the catch-all terracotta second gives exactly “sand on top, terracotta below.” Replace the surface_rule value with this complete rule:

data/mypack/worldgen/noise_settings/exaggerated.json (the surface_rule field, complete value)

{
  "type": "minecraft:sequence",
  "sequence": [
    {
      "type": "minecraft:condition",
      "if_true": {
        "type": "minecraft:stone_depth",
        "surface_type": "floor",
        "offset": 0,
        "add_surface_depth": false,
        "secondary_depth_range": 0
      },
      "then_run": {
        "type": "minecraft:block",
        "result_state": {
          "Name": "minecraft:red_sand"
        }
      }
    },
    {
      "type": "minecraft:block",
      "result_state": {
        "Name": "minecraft:terracotta"
      }
    }
  ]
}

How to read it:

  • The outer rule is a sequence: a list tried top to bottom.
  • The first list entry is a condition: its if_true is a stone_depth check with surface_type: floor (distance to the surface above), and its then_run is a block rule placing minecraft:red_sand. The add_surface_depth and secondary_depth_range fields belong to that condition; with offset: 0 and add_surface_depth: false this matches the topmost surface layer.
  • The second list entry is a bare block rule placing minecraft:terracotta. It has no condition, so it always matches, which is why it must come last: it’s the catch-all that paints everything the red-sand rule didn’t.

Try It! — bands of color. Real badlands stack several terracotta colors. You could add more condition entries before the catch-all, each using a y_above check at a different height to place orange_terracotta, yellow_terracotta, and so on, higher bands first. (The exact band pattern vanilla uses comes from the hard-coded minecraft:clay_bands_offset noise and the badlands rule type; you can’t perfectly reproduce it by hand, but you can fake stripes with y_above.)

Drop into the dimension again and the stone is now capped with red sand over terracotta: an exaggerated mesa.

Figure (to be captured). a tall mesa spire with a red-sand cap and terracotta body, generated by the surface rule

Carvers: caves and ravines

Terrain so far is solid where final_density is positive. Carvers dig back into that solid rock to make caves and canyons:

Configured carvers are used to add caves and canyons. They are referenced in biomes.”

That last sentence matters: a carver is not placed inside noise settings. It’s a separate file at data/<namespace>/worldgen/configured_carver/, and a biome points at it (you met biomes in Chapter 42). So the wiring is biome → carver, the same way a biome points at features.

A carver has a type and a config. There are three carver types:

Carver typeWhat it carves
cave“Carves a cave. A cave is a long tunnel that sometimes branches.”
nether_caveLike cave but “with a less frequency and wider tunnels,” and lava-filled below a level.
canyon“Carves a canyon.” (a ravine)

The shared config fields are: probability (“The probability that each chunk attempts to generate carvers,” 0 to 1), y (“The height at which this carver attempts to generate”), lava_level (the Y at/below which carved areas fill with lava), and replaceable (“Blocks that can be carved… a block ID, a block tag, or a list of block IDs”). A cave adds shape knobs: yScale, horizontal_radius_multiplier, vertical_radius_multiplier, and floor_level (“Change the shape of the cave’s horizontal floor”).

Here’s a big-cave carver:

data/mypack/worldgen/configured_carver/big_caves.json

{
  "type": "minecraft:cave",
  "config": {
    "probability": 0.15,
    "y": {
      "type": "minecraft:uniform",
      "min_inclusive": {
        "above_bottom": 8
      },
      "max_inclusive": {
        "absolute": 180
      }
    },
    "lava_level": {
      "above_bottom": 10
    },
    "replaceable": "#minecraft:overworld_carver_replaceables",
    "yScale": {
      "type": "minecraft:uniform",
      "value": {
        "min_inclusive": 0.7,
        "max_inclusive": 1.4
      }
    },
    "horizontal_radius_multiplier": {
      "type": "minecraft:uniform",
      "value": {
        "min_inclusive": 1.0,
        "max_inclusive": 2.0
      }
    },
    "vertical_radius_multiplier": {
      "type": "minecraft:uniform",
      "value": {
        "min_inclusive": 0.8,
        "max_inclusive": 1.3
      }
    },
    "floor_level": -0.4
  }
}

Two of those fields lean on small sub-formats worth knowing about:

  • The y and yScale/radius values use the height_provider, vertical_anchor, and float_provider sub-formats. Each is a little reusable JSON shape with its own dedicated wiki page (Height provider, Vertical anchor, Float provider). The shapes shown here (uniform with min_inclusive/max_inclusive, and above_bottom/absolute anchors) follow the standard vanilla convention; if a value is rejected, the height-provider page has the exact grammar.
  • #minecraft:overworld_carver_replaceables is the vanilla block tag for carve-able blocks. replaceable accepts “a block tag,” and that tag is the standard one the Overworld uses; confirm it exists in your version (or list block IDs directly, e.g. ["minecraft:stone", "minecraft:terracotta"]).

To make the cave actually appear, the biome your dimension uses must list it under carvers (Chapter 42’s biome file). For a custom biome that would be:

"carvers": ["mypack:big_caves"]

Modern Minecraft — nether_cave and canyon. Reuse the same file shape: switch type to minecraft:canyon for a ravine (its config swaps the cave’s radius knobs for a shape compound: distance_factor, thickness, horizontal_radius_factor, and so on), or minecraft:nether_cave for the wider, lava-floored Nether style.

Practice

These extend the files you just built, so keep working in mypack.

  1. Crank the exaggeration. In exaggerated.json, change the mul constant from 3.0 to 6.0 and reload into a fresh world. Then try 1.5. Notice how the same gradient-plus-noise produces gentle hills or absurd spikes depending on that one multiplier. (You’re tuning a mul density function, the one that “Multiplies two inputs.”)

  2. Add a third surface band. Insert a new condition entry into the surface-rule sequence, before the terracotta catch-all, that uses a y_above condition to place orange_terracotta above some height. Higher, more specific rules go first; the catch-all stays last. (You’re using the y_above condition and its anchor field from the conditions table.)

  3. Make a canyon carver. Copy big_caves.json to ravines.json, change type to minecraft:canyon, and replace the cave-only radius fields with the canyon’s shape compound (its fields: distance_factor, thickness, horizontal_radius_factor, vertical_radius_default_factor, vertical_radius_center_factor, width_smoothness). Add mypack:ravines to your biome’s carvers list. (If a vertical_anchor/height_provider value is rejected, check the height-provider wiki page for the exact shape.)

What Can Go Wrong

Your world is completely empty (void). Almost always final_density is evaluating to 0 or negative everywhere. Remember the rule: final_density: 0 → void; final_density: 1 → solid stone. Check that your y_clamped_gradient actually goes positive at low Y (from_value should be positive at from_y) and that you didn’t multiply the whole thing to nothing.

The pack won’t load / the noise block is rejected. Minecraft requires min_y and height to be divisible by 16, with min_y + height not exceeding 2032, and size_horizontal/size_vertical between 0 and 4. A height of 385 (not divisible by 16) will fail. Stick to multiples of 16.

A type doesn’t exist / silently wrong terrain. Density-function, surface-rule, and condition types are exact strings. minecraft:y_clamp_gradient (missing the ed) or minecraft:sequnce won’t match anything. When something generates as flat stone or refuses to load, re-check every type against the tables in this chapter: they’re the exact strings Minecraft expects.

The surface rule paints the wrong order. A sequence stops at the first match. If your catch-all block rule (no condition) is at the top, it wins every time and the conditional bands below it never run. Put the most specific conditions first and the bare catch-all last.

The cave never appears. A carver is referenced from a biome, not from noise settings. If you created the configured_carver file but never added it to a biome’s carvers list, nothing carves. Also check probability isn’t 0.


What You Know Now — Part XI complete

That’s the bottom of the rabbit hole, and the end of Part XI. Across these chapters you went from placing structures (Chapter 40) to defining them (41), from using biomes to customizing them (42), to scattering features (43), to building whole dimensions (44), and now, in this chapter, to sculpting the raw land itself.

You can now:

  • read a noise settings file and name its parts: sea_level, default_block/default_fluid, the noise block, the noise_router, and the surface_rule;
  • write a final_density from a y_clamped_gradient height gradient plus a noise, combined with add/mul, and exaggerate or calm the terrain with a single multiplier;
  • recognize that the biome-parameter router fields (temperature, vegetation, continents, erosion, depth, ridges) shape biome placement, not terrain, and can stay 0 for a simple dimension;
  • build a surface rule as a sequence of conditionblock rules, using stone_depth and y_above to paint layered blocks like red sand over terracotta;
  • define a carver (cave, nether_cave, or canyon) and wire it to a biome’s carvers list.

You’ve also learned the most advanced data-pack skill of all: knowing where a topic gets deep enough to deserve its own study. Several times this chapter we reached fields that open onto specialties of their own (the exact noise math, splines, vein design, the depth formulas, the height-provider sub-formats), and the right move was to name them, use the simplest working form, and point you to where the full spec lives, rather than fake a half-answer. That instinct will serve you in every corner of data packs.

Where to go next. This is the deepest the book goes. If terrain generation grabbed you, the vanilla data pack’s own worldgen/noise_settings/overworld file is the master class: open it and you’ll now recognize the final_density, the router fields, and the surface-rule sequence, even if its splines are beyond what we built. Part XII moves to finishing and sharing the packs you’ve made.

Chapter 46 — Pack Versioning and Compatibility

Part XII — Finishing and Sharing. You’ve built almost everything a data pack can hold. The last two chapters are about shipping it: making it load on the right versions of the game (this chapter), then packaging and sharing it (Chapter 47). After the deep end of world generation, this is a gentle, practical chapter, mostly one small file, pack.mcmeta, that you already know.

What You’ll Build

Way back in Chapter 9 you wrote a pack.mcmeta file with two version fields, min_format and max_format, and a description. You set both fields to 88 and moved on. This chapter goes back to that file and finishes the job.

By the end you’ll understand:

  • what a pack format number is, and how to read off the one for your game version;
  • how min_format and max_format declare a range of versions your pack supports, and how to write that range two different ways;
  • when the old pack_format / supported_formats fields belong (almost never) and the strict rule about leaving them out;
  • how to add an overlay — a folder of replacement files the game only loads on certain versions;
  • how to add a filter — patterns that hide files coming from other packs;
  • how to switch on a feature flag for experimental content.

And you’ll assemble all of that into one mypack/pack.mcmeta that loads cleanly across more than one Minecraft version.

Deepening, not repeating. Chapters 9 and 28 taught you that min_format/max_format exist and what value to use. This chapter teaches you why there are two of them, how to make them span a range, and the four optional sections that live alongside them. If min_format: 88 already feels familiar, good: you’re ahead.

Pack format numbers: what they are

Every version of Minecraft can read packs up to a certain age, no older and no newer. The way the game tracks this is a single whole number called the pack format (sometimes called the pack version). It’s the number you put in pack.mcmeta to describe which Java versions your pack is compatible with. Each game version declares which pack formats it supports.

Why does this exist? Because the file formats change between versions. The shape of a loot table, a predicate, or an item component is not the same in every release. The pack format number is how the game checks, before it even reads your files, whether your pack was built for the rules it knows. As the wiki puts it: pack formats help Minecraft identify whether a pack was built for the correct version of the game.

Here’s what happens when the number is wrong. If the format in your pack.mcmeta is higher than the format the game supports (that is, your pack was built for a newer Minecraft than the one running), the pack shows up as “incompatible” in the pack list, and the game refuses to load it. This is a safety feature: it stops new pack features from causing crashes or missing content in an older game that doesn’t understand them.

Under the Hood (skippable). Each pack format corresponds to specific internal changes in how Minecraft reads data: updates to registries, loot tables, tags, advancements, and model syntax. For example, the wiki notes that upgrading from format 12 to 15, several JSON structures (such as predicates and item components) changed to match new engine rules. Data packs and resource packs use the same numbering system but apply it to different things: data packs to gameplay logic (structures, recipes, tags, loot tables, functions), resource packs to textures, models, block states, sounds, and text.

Finding your version’s number

You don’t have to memorize the table. The game will tell you its pack format directly. Two ways:

  • Run the version command. (In a function file that’s a line reading version; in chat it’s /version.)
  • Press the F3 + V debug hotkey.

Either one reports the pack format your current game supports. That’s the number you build for.

Modern Minecraft. Older tutorials hand you a fixed number like “use pack_format: 48.” That advice rots, because the number changes every version. Reading the number off your own game with /version or F3 + V is the habit that doesn’t go stale.

Where to look up a format number. Pack formats change every version, so the most reliable “format N = Minecraft version X” lookup is the one your own game reports. The two values worth remembering are 88 (recommended for Minecraft 1.21.9 and newer) and 48 (for 1.21.8 and earlier). Appendix B collects the common ones; for the full, always-current table, see the wiki’s Pack format page, and for your exact version just run /version.

min_format and max_format: the modern range

Since the snapshot 25w31a, the pack format is described with two fields working together, min_format and max_format, instead of the single old pack_format field. This applies to both data packs and resource packs.

The idea is a range. min_format is the oldest format your pack supports; max_format is the newest. The game loads your pack only if its own format falls inside that range. This is the pack.mcmeta you already have in mypack from Chapter 9, built for exactly one version, 1.21.9+:

mypack/pack.mcmeta

{
  "pack": {
    "description": "Example datapack for Minecraft 1.21.9",
    "min_format": 88,
    "max_format": 88
  }
}

This is the recommended configuration for 1.21.9 and newer. With both fields set to 88, the pack supports exactly format 88.

Two ways to write a version

Each of min_format and max_format can be written in two forms:

  • A single integer, like 88. This is read as a major version with the lowest minor version. Put another way: a value 82 (or [82]) is exactly the same as [82, 0].
  • A list of two integers, [major, minor], like [88, 0]. The first number is the pack (major) version, the second is a minor version. For min_format, a single integer means “that major version, minor 0.” For max_format, a single integer means “that major version, any minor version.”

So if you wanted to be explicit and spell out the minor versions, the same pack could read:

mypack/pack.mcmeta

{
  "pack": {
    "description": "Example datapack for Minecraft 1.21.9",
    "min_format": [88, 0],
    "max_format": [88, 0]
  }
}

For most packs the single-integer form is all you’ll ever write. The [major, minor] form exists for the rare case where a pack format gets a minor bump within the same major version and you need to pin to it precisely.

Try It! Open your game, run /version, and read off the supported pack format. If it matches the min_format/max_format in your mypack/pack.mcmeta, the pack is in range and loads. If your game reports a higher number than your max_format, raise max_format to match and /reload.

The legacy fields: pack_format and supported_formats

Before 25w31a, packs used a single field called pack_format: just one number, no range. You’ll still see it everywhere in older tutorials and old packs:

(an old-style pack.mcmeta, for 1.21.8 and earlier)

{
  "pack": {
    "description": "Example datapack for Minecraft 1.21",
    "pack_format": 48
  }
}

That’s the streamlined form for Minecraft 1.21.8 and earlier, with pack_format set to 48. There was also a field called supported_formats for declaring more than one supported version: it can be a single integer like 42, a two-element list like [42, 45], or an object { "min_inclusive": 42, "max_inclusive": 45 }.

Here’s the part that trips people up. In current Minecraft, min_format/max_format is the real mechanism. The old pack_format and supported_formats fields are only kept around for backwards compatibility, so that a single pack can also be loaded by versions of the game older than the min/max scheme. And there’s a strict rule attached to them:

These deprecated fields must be absent if the pack does not support old versions, but must be present if the pack does support old versions.

“Old versions” here means a data pack format below 82 (and, for resource packs, below 65). So:

  • Your pack is new-only? (Anything in the 1.21.9-and-up world.) Then pack_format and supported_formats must not appear at all. Use only min_format/max_format. This is the normal case, and it’s why mypack/pack.mcmeta has neither field.
  • Your pack genuinely needs to run on a pre-82 version too? Then you add the legacy fields and the modern ones, and the legacy field’s range has to match the major versions in min_format/max_format.

A pack that supports both old and new might look like this:

(a pack.mcmeta that supports both an old version and a new one)

{
  "pack": {
    "description": "Supports old and new",
    "min_format": 48,
    "max_format": 88,
    "supported_formats": { "min_inclusive": 48, "max_inclusive": 88 }
  }
}

Modern Minecraft. If you’re following an old tutorial that tells you to write "pack_format": 15, stop. On current Minecraft a single pack_format on a new-only pack is exactly the wrong thing: the rule above says it must be absent. Replace it with min_format/max_format. Only reach for the legacy fields if you have a concrete reason to support a years-old version.

Overlays: different files for different versions

What if format 88 wants a recipe written one way, and an older format 87 wants the same recipe written a slightly different way? You don’t want two whole separate packs. You want one pack that quietly swaps a few files depending on which version is loading it. That’s an overlay.

An overlay is a sub-pack: its own little data/ (and, in resource packs, assets/) folder that sits inside your pack’s root directory and gets applied over the normal contents, but only for a chosen format range. You declare overlays in pack.mcmeta under an overlays section. Each overlay entry has:

  • a directory — the folder name holding the overlay’s files (allowed characters: lowercase letters a-z, digits 0-9, underscore _, and hyphen -);
  • a min_format and max_format: the same kind of range as the main pack section, saying which game versions this overlay applies on.

Here’s mypack with one overlay that targets the previous format, 87:

mypack/pack.mcmeta

{
  "pack": {
    "description": "Example datapack for Minecraft 1.21.9",
    "min_format": 87,
    "max_format": 88
  },
  "overlays": {
    "entries": [
      {
        "directory": "v87",
        "min_format": 87,
        "max_format": 87
      }
    ]
  }
}

Two things changed. The main pack range now spans 87 to 88, so the pack itself loads on both versions. And the overlays.entries list holds one overlay whose directory is v87, active only on format 87.

The overlay’s files live in a folder named exactly like the directory value, sitting in the pack root next to data/. Inside it, you mirror the normal data/ structure: its own data/ (and assets/) folders. So if you want to override one recipe on format 87, the tree looks like this:

mypack/
├── pack.mcmeta
├── data/
│   └── mypack/
│       └── recipe/
│           └── magic_sword.json        ← the format-88 version (normal pack)
└── v87/
    └── data/
        └── mypack/
            └── recipe/
                └── magic_sword.json     ← the format-87 version (overlay)

When the game runs on format 87, it loads the normal pack and then applies the v87 overlay on top, so the overlay’s magic_sword.json wins. On format 88 the overlay’s range doesn’t match, so it’s skipped and the normal file is used. One pack, two behaviors, chosen automatically.

Important — no pack.mcmeta inside the overlay. Inside an overlay directory, the game ignores any pack.mcmeta and pack.png. The overlay is configured entirely from the main pack.mcmeta’s overlays section. Don’t put a second pack.mcmeta in v87/; it does nothing.

Under the Hood (skippable). The order of the entries list matters: the first overlay in the list is applied first. If two overlays both match the running version and both touch the same file, the later one in the list ends up on top. With a single overlay, as here, order doesn’t come up.

Filters: hiding files from other packs

Data packs stack. When several packs are enabled, a higher-priority pack can sit on top of a lower-priority one, and files merge. Sometimes you want to remove a file that a pack below you provides, not replace it, just make the game act as if it isn’t there. That’s what a filter does.

A filter is a pack.mcmeta section with a block list. Each entry is a pattern, and any file that matches one of the patterns is treated as if it was not present in the pack at all, for any pack applied below this one.

The patterns are regular expressions. A regular expression (or regex) is a small language for describing text patterns: a way to say “any path that looks like this” instead of naming one exact file. (Full regex syntax is a big topic; we’ll keep to simple patterns here.) Each pattern entry can have:

  • a namespace — a regex for the namespace of files to hide. If you leave it out, it applies to every namespace.
  • a path — a regex for the file paths to hide. If you leave it out, it applies to every file.

Here’s a filter that blocks everything in the minecraft namespace under the loot_table/ path coming from lower packs. Handy if you want your loot tables to be the only ones that survive:

mypack/pack.mcmeta

{
  "pack": {
    "description": "Example datapack for Minecraft 1.21.9",
    "min_format": 88,
    "max_format": 88
  },
  "filter": {
    "block": [
      {
        "namespace": "minecraft",
        "path": "loot_table/.*"
      }
    ]
  }
}

The path value loot_table/.* is a regex meaning “the text loot_table/ followed by any characters,” or in plain terms, every file inside the loot_table/ folder. Combined with namespace: "minecraft", this hides all vanilla-namespace loot tables that any lower-priority pack tries to contribute.

What Went Wrong? A filter only affects packs applied below this one. It does not hide your own files, and it does not touch packs that load with higher priority than yours. If a file you expected to disappear is still there, check the load order with /datapack list: the pack providing that file may actually be above yours.

Feature flags: turning on experimental content

Some content in Minecraft is gated behind a feature flag: a switch that turns on an experimental feature that isn’t part of the normal game yet. A feature flag is named by a resource location (the namespace:path form you’ve used since Chapter 8). You enable feature flags from pack.mcmeta under a features section with an enabled list:

mypack/pack.mcmeta

{
  "pack": {
    "description": "Example datapack for Minecraft 1.21.9",
    "min_format": 88,
    "max_format": 88
  },
  "features": {
    "enabled": [
      "minecraft:some_experimental_feature"
    ]
  }
}

Each string in enabled is the resource location of a feature flag.

There’s one big catch, and it’s important. Feature flags can only be enabled through a data pack when the world is being created. You cannot switch a feature flag on for an existing world by enabling a pack later. If you try to /datapack enable a pack that requires a feature flag that wasn’t turned on at world creation, the game refuses with: “Pack <name> requires enabling a feature flag that is not enabled via data pack when creating world.” So feature-flag packs have to be present from the moment the world is made, in place at world creation and kept there.

Finding the real flag names. The mechanism is always the same (features.enabled, a list of feature-flag resource locations), but the exact flag IDs change from version to version, so the example above uses a placeholder name. To find the real flags for your version, check the experimental-features list in your game’s Create World screen: those toggles are exactly what features.enabled mirrors.

The assembled pack.mcmeta

Putting the optional sections together, here’s a mypack/pack.mcmeta that uses all four ideas at once: a version range, an overlay for the older format, a filter, and a feature flag.

mypack/pack.mcmeta

{
  "pack": {
    "description": "My data pack — works on more than one version",
    "min_format": 87,
    "max_format": 88
  },
  "overlays": {
    "entries": [
      {
        "directory": "v87",
        "min_format": 87,
        "max_format": 87
      }
    ]
  },
  "filter": {
    "block": [
      {
        "namespace": "minecraft",
        "path": "loot_table/.*"
      }
    ]
  },
  "features": {
    "enabled": [
      "minecraft:some_experimental_feature"
    ]
  }
}

Read it top to bottom: pack says “I support formats 87 through 88.” overlays says “on format 87, also apply the files in the v87/ folder.” filter says “hide vanilla loot tables coming from any pack below me.” features says “this world needs this experimental feature flag turned on.” Every one of those sections is optional: drop any you don’t need. For most packs, you’ll only ever write the pack section, exactly as in Chapter 9.

Figure (to be captured). the data pack list showing the pack loaded without an “incompatible” warning, with its description visible on hover

Practice

These extend the mypack/pack.mcmeta you just built. Test each by running /reload (or reopening the world) and checking /datapack list: your pack should appear enabled, never incompatible.

  1. Widen the range. Change the main pack section to min_format: 48, max_format: 88 and add the matching legacy field so the pack can also load on a pre-82 version: "supported_formats": { "min_inclusive": 48, "max_inclusive": 88 }. Remember the rule: the legacy field is required now precisely because the pack supports old versions. Then change it back to a new-only pack and confirm you removed supported_formats again.

  2. Add a second overlay. Create a v48/ folder mirroring data/, and add a second entry to overlays.entries with directory: "v48", min_format: 48, max_format: 48. Put a different copy of one file inside it. Now your pack swaps that file three ways: format 48, format 87, and the normal format-88 file.

  3. Filter by namespace only. Write a filter.block entry that leaves out path entirely and sets namespace: "mypack". Since a missing path applies to every file, this hides all mypack files from packs below you. Predict what that does before you reload, then check.

What Can Go Wrong

What Went Wrong? “My pack shows as incompatible.” Your max_format is lower than the format your game supports: you built for an older version than you’re running. Run /version to read your game’s actual pack format and raise max_format to at least that number. The opposite mistake (min_format higher than the game) also makes it incompatible: the game is older than your floor.

What Went Wrong? “I added pack_format and now there are warnings.” On a new-only pack the legacy fields must be absent. If you copied "pack_format": 88 from an old tutorial alongside your min_format/max_format, delete the pack_format line. Only keep legacy fields when you genuinely support a pre-82 version, and then they must match your min/max range.

What Went Wrong? “My overlay isn’t doing anything.” Three things to check. First, the overlay folder name must match the directory value exactly (lowercase letters, digits, _, - only). Second, the overlay’s range has to include the version you’re testing on. If you’re on format 88 but the overlay is min_format: 87, max_format: 87, it correctly does nothing. Third, don’t put a pack.mcmeta inside the overlay folder; it’s ignored, and the overlay is configured only from the main pack.mcmeta.

What Went Wrong? “My feature-flag pack won’t enable.” Feature flags can only be turned on at world creation. If the game says the pack “requires enabling a feature flag that is not enabled,” the world was made without that flag. You can’t add it to an existing world by enabling the pack. Make a fresh world with the pack (and its flag) present from the start.


Next: Chapter 47 — Publishing and Sharing Your Work. You can load your pack on the right versions; now you’ll give it an icon, a clean namespace, a license, and a home on a sharing site.

Chapter 47 — Publishing and Sharing Your Work

Part XII — Finishing and Sharing. The final chapter.

What You’ll Build

In this chapter you don’t build a new feature. Instead you get the pack you’ve been building since Chapter 9 ready to hand to other people. You’ll write a clear description (the blurb that shows next to your pack in-game), add a pack.png icon, tidy your folders so a stranger can read them, make sure your pack plays nicely alongside other people’s packs, package it as a .zip, and learn where people share data packs. You’ll also meet two ideas that will serve you far beyond Minecraft: licensing (telling people how they’re allowed to use your work) and version control with Git (keeping a safe history of every change). By the end you’ll have a finished, shareable pack, and you’ll have finished the book.

This is mostly about polish and good habits, not new mechanics. Some of it (the sharing websites, licensing, Git) is ordinary real-world advice that isn’t part of Minecraft itself. It’s marked as general guidance where it appears.


Concepts

A pack is just a folder or a .zip

You’ve known this since Chapter 7, but it matters now: a data pack is either a folder or a .zip file containing a pack.mcmeta file. That’s the whole definition. The pack.mcmeta (the small marker file you wrote in Chapter 9) is what makes a plain folder count as a real pack: it is the only mandatory file. Everything else (your functions, recipes, loot tables) is optional from the game’s point of view.

That single fact is why sharing is easy. To give your pack to a friend, you hand them the folder (or, better, a .zip of it) and they drop it into their world’s datapacks folder, exactly the way you’ve been loading your own pack all along.

Modern Minecraft — Older guides sometimes talk about “installing” a data pack as if it were a complicated program. It isn’t. A data pack is data, not a program: it’s a folder of text files the game reads. “Installing” it just means putting that folder (or .zip) where the game looks for it.

The description: the blurb players read

Your pack.mcmeta already has a description field. The game shows that description as a text component (the styled-text format from Chapter 5) that appears when you hover over the pack’s name in the pack list, and when you view the pack in the Create World screen. So it’s the first thing anyone sees about your pack.

Up to now your description has probably been something plain like "My first data pack". For a pack you’re sharing, make it do a job: say what the pack does in one short line. Because it’s a text component, you can give it color and style, just like the messages you styled in Chapter 5.

Here’s the pack.mcmeta for the pack you started in Chapter 9, with a share-ready description:

mypack/pack.mcmeta

{
  "pack": {
    "description": [
      { "text": "Sunforged", "color": "gold", "bold": true },
      { "text": " — custom drops, recipes & more", "color": "white" }
    ],
    "min_format": 88,
    "max_format": 88
  }
}

Two things to notice. First, the description is an array of text-component pieces (exactly the list form you learned in Chapter 5), so part of it is gold and bold and part is plain white. A single plain string (like "description": "My first data pack") is still perfectly valid; the array just lets you add color. Second, min_format and max_format are the version fields from Chapter 9 and Chapter 46: they tell the game which Minecraft versions your pack is built for. Leave them set the way Chapter 46 taught.

Try It! Write three different one-line descriptions for your pack, then read each one out loud. Pick the one that would make you click “download” if you saw it in a list of fifty packs. Short and specific beats long and vague.

The icon: pack.png

Right next to the pack.mcmeta, in the same top-level folder, you can put a file named pack.png. This is the picture displayed next to the data pack in the “Data Pack Selection” screen. (Resource packs work the same way: a pack.png shows next to them in the “Select Resource Packs” screen.) It’s optional (your pack works fine without one), but a pack with an icon looks finished, and on sharing sites a good icon is what makes someone stop scrolling.

So the very top of your pack now looks like this:

mypack/
  pack.mcmeta      ← the marker file (required)
  pack.png         ← your icon (optional)
  data/
    mypack/
      function/
      recipe/
      loot_table/
      ...

You know where pack.png goes and what it’s for. The image’s size and shape are up to you, with one piece of plain guidance: the in-game pack icons are shown as small squares, so a square image reads best. Make it square, keep it simple, and you’re set.

Try It! Make a simple square icon for your pack in any image editor — even a single bold symbol on a solid background works. Save it as pack.png in your pack’s top folder, reload, and open the Data Packs screen to see it appear.

Organizing files so a human can read them

The game doesn’t care how tidy your folders are; it finds files by their path, not by how neatly they’re arranged. Recall the rule from Chapter 9 and Chapter 14: a file at data/<namespace>/<registry name>/<path>.json is loaded into that registry with the ID <namespace>:<path>, and both the registry name and the path can contain slashes, which makes extra sub-folders. That last part is your tidiness tool.

Because the path can contain slashes, you can group related files into sub-folders without breaking anything. Compare a flat pile of loot tables:

data/mypack/loot_table/husk.json
data/mypack/loot_table/zombie.json
data/mypack/loot_table/mystic_ore.json
data/mypack/loot_table/treasure_chest.json

…with the same files grouped by what they’re for:

data/mypack/loot_table/entities/husk.json
data/mypack/loot_table/entities/zombie.json
data/mypack/loot_table/blocks/mystic_ore.json
data/mypack/loot_table/chests/treasure_chest.json

Both work. The second one tells a reader (including future-you) at a glance which tables are mob drops, which are block drops, and which are chests. The only thing to remember is that moving a file changes its ID: mypack:husk becomes mypack:entities/husk, so any function or table that referenced the old ID has to be updated. (This is the same path-equals-ID rule you’ve used since Chapter 9, nothing new, just applied on purpose.)

A few plain-sense habits make a shared pack pleasant to read:

  • Use clear file and function names. husk_reward tells a reader more than func3.
  • Comment your functions. A # line at the top of an .mcfunction (Chapter 9) saying what it does costs nothing and saves a reader minutes.
  • Keep one namespace, and make it yours. Everything you’ve built lives under mypack. When you share for real, swap that for a namespace that’s clearly yours. That leads straight to the next idea.

Compatibility: don’t step on other packs

Here’s a problem you’ve been protected from all book long, because you’ve only run your own pack. The moment someone runs your pack alongside somebody else’s, files can collide. The rule for how collisions resolve is blunt: if a file exists in multiple data packs, only the file in the last data pack is used. This is called overriding the earlier file.

So if your pack and another pack both contain a file at exactly data/minecraft/loot_table/entities/zombie.json, whichever pack loads later wins and the other one’s version is silently ignored. Two well-meaning packs can break each other without either author doing anything “wrong.”

The fix is the idea you met in Chapter 8 and have used on every file since: namespaces. Data is organized into namespaces specifically to avoid files from different packs unintentionally interfering with each other. A file at data/mypack/... and a file at data/coolpack/... can never collide, because their paths differ. So the single most important compatibility rule is:

Keep all of your own content under your own unique namespace. Don’t reuse mypack if you publish; that’s the book’s teaching name and other learners use it too. Pick something distinctive (your username, your project’s name) in the lowercase-underscore style from Chapter 8, and put everything there.

There’s one place you can’t avoid sharing a path with everyone else, and it’s worth knowing. When you intentionally change a vanilla thing (like the zombie-drops override from the projects in Part IX) you must use the minecraft namespace, because that’s where vanilla’s own files live. Two packs that both override data/minecraft/loot_table/entities/zombie.json will clash, and only the later one survives. There’s no perfect cure, but you can reduce the damage:

  • Prefer adding over replacing. Tags are the friendly exception: tag files without "replace": true merge their content with the files loaded from earlier packs. So if you add your function to minecraft:tick (Chapter 14) without "replace": true, your entry joins everyone else’s instead of wiping them out. Whenever you can express an addition as a tag, you avoid the override fight entirely.
  • Mention what you override. If your pack must replace a vanilla file, say so in your description or notes, so someone combining packs knows where a conflict could come from.

Under the Hood (skippable) — Which pack counts as “last”? Data packs load in a load order that you can see and change on the Data Packs screen and with the /datapack command, and that this order is stored in the world’s level.dat file. Packs lower in the list load first; packs above them load later and therefore win ties. This is the same load-order idea behind tag merging and file overriding: it’s all one system.

Packaging: zipping your pack

To share a pack as a single file, turn the folder into a .zip. Remember the definition: a data pack is a folder or a .zip containing a pack.mcmeta. The one thing that trips everyone up is what’s at the top of the zip.

The pack.mcmeta (and pack.png, and the data/ folder) must sit at the root of the zip, not inside an extra wrapper folder. In other words, when you open the zip you should immediately see pack.mcmeta, not a folder you have to click into first. If you zip the containing folder by mistake, the game opens the zip, sees a folder instead of pack.mcmeta, and doesn’t recognize it as a pack.

What Went Wrong? “I zipped my pack and the game won’t list it.” Almost always this is the wrapper-folder mistake. Open the .zip and check: do you see pack.mcmeta right away? If instead you see a single folder named after your pack, you zipped one level too high. Go into the pack folder, select pack.mcmeta + pack.png + data/ together, and zip those.

Under the Hood (skippable) — A resource pack zips the same way, with pack.mcmeta at the root. The book’s resource-pack chapters (Part VIII) even used a special case of this: a resource pack zipped and renamed resources.zip, dropped into a world folder, rides along with that world. Same packaging idea, different destination.

Where to share (general guidance — not Minecraft-specific)

Once your pack is a tidy .zip with a good description and icon, you can put it where people look for data packs. The big community sites are:

  • Modrinth — a modern, open hosting site for Minecraft content (mods, resource packs, and data packs). Clean, free, and creator-friendly.
  • Planet Minecraft (PMC) — a long-running community site with a large audience for data packs, maps, and skins; strong for getting comments and feedback.
  • CurseForge — one of the oldest and largest Minecraft content hubs, widely used by launchers and modpacks.

These are real-world websites, not part of Minecraft, so the exact upload steps and rules live on each site and change over time. Check their own “how to upload” help pages. Whichever you pick, the same things make a listing good: a clear title, the one-line description you already wrote, your pack.png (or a nicer banner), a screenshot or two of your pack in action (the screenshots this book kept asking you to take), and a short list of what the pack does and which Minecraft version it needs (your min_format/max_format from Chapter 46 tells you that version).

Try It! Before uploading anywhere, write a tiny README, a plain text file that lists what your pack does, how to install it (drop the folder/zip in the world’s datapacks folder, run /reload), and which Minecraft version it targets. You’ll paste most of it straight into the upload form.

Licensing: telling people what they may do (general guidance)

When you publish something you made, other people will naturally wonder: Can I use this? Can I change it? Can I put it in my own pack? A license is a short statement that answers those questions in advance, so nobody has to guess or ask.

This is general creative-work advice, not a Minecraft feature, so keep it simple:

  • If you say nothing, people are left unsure, and cautious people won’t reuse or build on your work at all. Silence usually reads as “ask first,” which most people won’t.
  • If you want people to freely use and remix your pack, pick a well-known permissive license (the Creative Commons family is popular for content like this) and include its text or a link, plus a line asking for credit if that matters to you.
  • If you want to keep tighter control, say what is allowed in plain words: for example, “you may use this in your own maps but please credit me and don’t re-upload it as your own.”

The point is just to add a short, friendly note (often a LICENSE or README file beside your pack) so the people who admire your work know how they’re welcome to use it. When you build on other people’s packs, return the favor: check their license, and give credit.

A first look at version control with Git (general guidance)

Here’s the last new idea in the book, and it’s a gift to your future self.

You’ve felt the pain already: you change a working function, it breaks, and you can’t quite remember what it looked like before. Version control is a tool that fixes exactly that. It keeps a complete history of your project, so you can see every change you’ve made and rewind to any earlier version whenever you want. Git is the most widely used version-control tool, and it works beautifully on a data pack, because a data pack is just a folder of text files, precisely the kind of thing Git is built to track.

This is general programming knowledge, not a Minecraft feature, so here’s just enough to get the idea:

  • You initialize a Git “repository” in your pack folder once. From then on, Git watches that folder.
  • As you work, you take snapshots called commits. Each commit saves the exact state of every file with a short message like "add Sunforged Blade recipe" or "fix husk loot chance".
  • If a change goes wrong, you can look back through your commits and restore an earlier one. Your history is a safety net: nothing good is ever truly lost.
  • You can push your repository to a site like GitHub or GitLab to back it up online and let others see (and suggest improvements to) your code.

Think of it as /reload for your project’s whole history: instead of just reloading the current files, you can jump to how they looked yesterday, last week, or right before everything broke. You don’t need it to make a great pack, but the day you accidentally delete two hours of work, you’ll be very glad you took the snapshot.

Try It! If you’re curious, install Git and run its “init” and “commit” steps in your pack folder, then make a small change and commit again. Look at the history. Even on a tiny pack, seeing your own change-log appear is a small revelation, and it’s a skill that carries straight into real software work.


Walkthrough: getting mypack ready to ship

Let’s turn the pack you’ve built across this whole book into something you could hand to a stranger. Nothing here changes how the pack behaves; it’s all finishing.

1. Write a real description. Open your pack.mcmeta and replace the plain description with a styled one-liner that says what the pack does. Use the array form so you can add color:

mypack/pack.mcmeta

{
  "pack": {
    "description": [
      { "text": "Sunforged", "color": "gold", "bold": true },
      { "text": " — custom mob drops, recipes, and a mystic ore", "color": "white" }
    ],
    "min_format": 88,
    "max_format": 88
  }
}

2. Add an icon. Make a square image, save it as pack.png, and drop it in the top folder right next to pack.mcmeta.

3. Tidy the folders. Group your loot tables into entities/, blocks/, and chests/ sub-folders as shown earlier, and remember to update any IDs that moved. Add a one-line # comment to the top of each function saying what it does.

4. Rename your namespace (when publishing for real). Everything under data/mypack/ becomes data/<your-name>/, and every mypack:... reference updates to match. The book keeps mypack so all chapters line up, but a published pack should wear a namespace that’s clearly yours.

5. Reload and test. Run /reload and confirm everything still works after the moves and renames. This is exactly the “change one thing, reload, check” habit from Chapter 10. Tidying is a great way to accidentally break a path, so test.

6. Zip it. Go into the pack folder, select pack.mcmeta, pack.png, and data/ together, and compress those into a .zip (so pack.mcmeta is at the zip’s root). That single file is your shareable pack.

7. Write a README and pick a license. A short text file with what-it-does, how-to-install, version, and a line about how people may use it. Now you’re ready to upload to Modrinth, Planet Minecraft, or CurseForge.

Figure (to be captured). the Data Packs selection screen showing the finished mypack with its new gold/white description on hover and its pack.png icon beside the name


Practice

  1. Three descriptions, one winner. Write three different pack.mcmeta descriptions for your pack (one plain, one colorful, one funny), load each, and hover to compare them in the list. Keep the one that best tells a stranger what the pack is in a single glance.

  2. Make and test a conflict. Copy your pack folder, rename the copy’s folder, and keep both namespaces the same on purpose. Load both, reload, and watch the override behavior in action: the later pack’s same-path files win. Then fix it by giving the copy its own namespace and confirm both packs now work side by side. (This is the single most valuable thing to feel rather than just read.)

  3. Tag instead of override. Find a place where your pack replaces a vanilla file, and see whether you can express the same intent by adding to a tag without "replace": true instead, so your change merges with other packs’ instead of overriding them. (Not everything can be done this way; the goal is to notice when it can.)

  4. Ship it for real. Zip your pack correctly, write a README, choose a license, and, if you’d like, actually upload it to one of the community sites. Then download your own pack back from the site, install it into a fresh world, and make sure it works. Shipping something and watching it install cleanly is the best possible final exercise.

  5. Snapshot your project. Initialize Git in your pack folder and make your first commit. Change one function, commit again, and look at the two-entry history you just created.


What Can Go Wrong

  • The zip has a wrapper folder. You compressed the containing folder instead of the pack’s contents, so the zip opens to a folder instead of pack.mcmeta, and the game won’t list the pack. Re-zip from inside the pack folder so pack.mcmeta sits at the root. (Most common publishing mistake by far.)

  • No pack.mcmeta at the top, or it’s misplaced. The pack.mcmeta is the only mandatory file, and its presence is what identifies the folder or zip as a pack. If it’s missing, misspelled, or buried in a sub-folder, the game sees no pack at all. Check it’s spelled exactly pack.mcmeta and lives at the top level.

  • A reused namespace causes a silent clash. Two packs sharing a namespace (or both overriding the same minecraft: file) collide, and only the later one’s files take effect, with no error message, because overriding is normal, expected behavior. If something stops working only when two packs are loaded together, suspect a path/namespace collision and give your content a unique namespace.

  • A broken description hides the pack. The description is a text component (Chapter 5), so a JSON typo there (a missing comma, an unclosed bracket) can make the whole pack.mcmeta fail to parse, and a pack with an unreadable pack.mcmeta won’t load. If your finished pack vanishes from the list right after you styled the description, re-check that JSON first.


What You Know Now

You can take a working data pack and turn it into something other people can find, trust, and run. You can write a description that actually sells the pack and add a pack.png icon; you can organize your files so a human can read them; you understand why packs collide (same path, later pack wins) and you know that keeping everything under your own namespace is what prevents it, with tags as the merge-friendly exception. You can package a pack as a .zip with pack.mcmeta at its root, and you know the community sites (Modrinth, Planet Minecraft, CurseForge) where data packs are shared. And you’ve met two ideas that outlast this book: licensing, so people know how they may use your work, and Git, so your project’s whole history is always recoverable.


A Word to End On

You started this book as someone who plays Minecraft. You’re ending it as someone who can make Minecraft: someone who looks at an item, a mob, a biome, a dimension, and sees the data files underneath, and knows how to write your own.

Think about how far that is. In the first three chapters you went from “I’ve never made a data pack” to a pack that loaded, announced itself, and added a recipe. From there you learned to talk to the game with commands tucked safely inside functions; to store and track information three different ways and choose the right one; to declare what things are with tags, recipes, loot tables, predicates, and advancements; to build custom items out of components instead of fighting raw NBT; to make functions that take arguments, return answers, and run on a schedule; to dress your creations up with resource packs; and to combine all of it into real projects. The bravest among you went further still, into data-driven enchantments, villager trades, dialogs, and the deep magic of world generation: biomes, structures, dimensions, and the noise and density functions that sculpt terrain itself.

All of that adds up to a way of seeing. Modern Minecraft is data-driven from top to bottom (“vanilla is just a data pack”) and you now think in that system instead of hacking around it. The same instinct that told you to add to a tag instead of overriding a file, or to reach for command storage instead of bending scoreboards, is exactly how the people who build the game think.

There will always be one more registry to explore, one more component, one more corner of worldgen. That’s the good news: you’ll never run out of things to make. When you hit something this book didn’t cover, you already know what to do: find the data file behind it, read what it expects, change one thing, /reload, and see what happens. That loop is the whole craft, and it’s yours now.

So pick an idea that’s been rattling around your head (the weird item, the silly minigame, the world that shouldn’t exist) and go build it. Then share it, so the next person who’s just discovering that Minecraft is made of files has something of yours to learn from.

Thanks for building alongside us. Now go make something only you would make.

— The End —

Appendix A — Command Quick Reference

Java Edition (current). This is a fast lookup for every command this book actually uses, in alphabetical order. Each entry gives the Java syntax (Bedrock forms are not shown), a one-line description, and the chapter(s) where the command appears.

Notation, the same as in the in-game help and on the wiki: <required> is an argument you must supply, [optional] may be left out, a|b means “choose one of a or b”, and ... means the command continues with more arguments. A leading / is shown when you type a command straight into chat; inside a function file you write the command without the slash.

Every syntax line below matches that command’s page on the wiki (Commands/<name>).


/advancementadvancement (grant|revoke) <targets> (everything | only <advancement> [<criterion>] | from <advancement> | through <advancement> | until <advancement>) — Grants or revokes an advancement (or a single criterion) for one or more players. (Ch 9, 19, 30, 31, 32, 34, 38)

/cloneclone [from <sourceDimension>] <begin> <end> [to <targetDimension>] <destination> [strict] [replace|masked|filtered <filter>] [force|move|normal] — Copies a region of blocks from one place (or dimension) to another. (Ch 2, 40)

/damagedamage <target> <amount> [<damageType>] [at <location>] [by <entity>] [from <cause>] — Deals a set amount of damage to the target entities, using the game’s normal damage rules. (Ch 21, 23, 36)

/datadata (get|merge|modify|remove) (block <pos>|entity <target>|storage <target>) ... — Reads, merges, modifies, or removes the NBT data of a block entity, entity, or command storage. (Ch 7, 8, 9, 10, 1, 3, 4, 11, 12, 13, 20, 21, 25, 27, 28, 33, 38, 40, 43)

/datapackdatapack (enable <name> [first|last|(before|after) <existing>] | disable <name> | list [available|enabled] | create <id> <description>) — Loads, unloads, lists, or creates data packs. (Ch 9, 10, 28, 34)

/dialogdialog (show <targets> <dialog> | clear <targets>) — Shows a dialog screen (from the minecraft:dialog registry or inline SNBT) to players, or clears it. (Ch 39)

/effecteffect (give <targets> <effect> [<seconds>|infinite] [<amplifier>] [<hideParticles>] | clear [<targets>] [<effect>]) — Gives or removes status effects on players and other entities. (Ch 8, 3, 4, 13, 18, 19, 27, 31, 32, 35, 36)

/enchantenchant <targets> <enchantment> [<level>] — Adds an enchantment to the item a player or mob is holding in its main hand. (Ch 35)

/executeexecute (align|anchored|as|at|facing|in|on|positioned|rotated|store|if|unless|run ...) ... — Runs another command with a changed executor, position, rotation, dimension, condition, or stored result. (Ch 3, 4, 5, 11, 12, 13, 14, 18, 25, 26, 27, 31, 32, 33, 44, 45)

/fillfill <from> <to> <block> [outline|hollow|destroy|strict|replace [<filter>]|keep] — Fills a cuboid region with a chosen block. (Ch 2, 40, 44)

/functionfunction <name> [<arguments> | with (block <pos>|entity <source>|storage <source>) [<path>]] — Runs the commands in a function file (or every function in a function tag), optionally passing macro arguments. (Ch 8, 9, 10, 1, 2, 3, 4, 5, 11, 12, 13, 14, 16, 17, 19, 20, 21, 22, 23, 25, 26, 27, 28, 30, 31, 32, 33, 34, 36, 38, 39, 40, 42, 45)

/gamerulegamerule <rule name> [<value>] — Sets or queries the value of a game rule. (Ch 10, 2)

/givegive <targets> <item> [<count>] — Gives one or more players an item (with optional item components and count). (Ch 8, 10, 1, 2, 5, 19, 21, 22, 23, 24, 26, 28, 29, 30, 31, 32, 34, 36, 38, 39, 40, 42)

/itemitem (modify (block <pos>|entity <targets>) <slot> <modifier> | replace (block <pos>|entity <targets>) <slot> (with <item> [<count>] | from (block <pos>|entity <target>) <slot> [<modifier>])) — Modifies or copies items in the inventory slots of blocks or entities. (Ch 9, 10, 14, 15, 16, 17, 19, 20, 21, 24, 31, 34, 37, 39)

/lootloot <TARGET> <SOURCE> (TARGET = give <players> | insert <pos> | spawn <pos> | replace block|entity <pos/target> <slot> [<count>]; SOURCE = fish <table> <pos> [<tool>] | loot <table> | kill <target> | mine <pos> [<tool>]) — Generates items from a loot table and gives, inserts, spawns, or replaces them. (Ch 7, 16, 17, 19, 31, 34, 37)

/particleparticle <name> [<pos>] [<delta> <speed> <count> [force|normal] [<viewers>]] — Creates particles at a position. (Ch 2, 4, 27, 31)

/placeplace (feature <feature> [<pos>] | jigsaw <pool> <target> <max_depth> [<pos>] | structure <structure> [<pos>] | template <template> [<pos>] [<rotation>] [<mirror>] [<integrity>] [<seed>] [strict]) — Places a configured feature, structure, structure template, or jigsaw structure. (Ch 40, 41)

/placefeatureplacefeature <feature> [<pos>] — Places a single configured feature at a position if its placement requirements are met. (Ch 43)

/playsoundplaysound <sound> [<source>] [<targets>] [<pos>] [<volume>] [<pitch>] [<minVolume>] — Plays a sound event to players at a location, with optional volume and pitch. (Ch 2, 19, 30, 31, 34)

/randomrandom (value|roll) <range> [<sequence>] (also random reset (* | <sequence>) [<seed>] [<includeWorldSeed>] [<includeSequenceId>]) — Generates a random integer in a range, or manages random sequences. (Ch 3, 16, 26)

/reloadreload — Reloads the world’s data packs without leaving the world. (Ch 8, 9, 10, 1, 2, 4, 5, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 32, 33, 34, 35, 36, 38, 39, 41, 42, 43, 44)

/returnreturn (<value> | run <command>) — Ends a function early, setting its return value and success; can also stop a forking execute. (Ch 25)

/saysay <message> — Broadcasts a plain-text message in chat to everyone on the server. (Ch 9, 10, 1, 2, 3, 4, 11, 12, 13, 14, 18, 20, 21, 23, 25, 27, 28, 31, 34, 36, 39)

/scheduleschedule function <function> <time> [append|replace] (also schedule clear <function>) — Runs a function (or function tag) after a delay, or clears a pending schedule. (Ch 26, 33)

/scoreboardscoreboard (objectives (add <objective> <criteria> [<displayName>]|remove <objective>|list|setdisplay <slot> [<objective>]|modify ...) | players (set|add|remove <targets> <objective> <score>|get <target> <objective>|operation <targets> <obj> <op> <source> <obj>|reset <targets> [<objective>]|enable <targets> <objective>|list ...)) — Creates and manages scoreboard objectives and the scores held against them. (Ch 5, 11, 12, 13, 25, 26, 27, 33)

/setblocksetblock <pos> <block> [destroy|keep|replace|strict] — Changes the single block at a position to another block. (Ch 2, 27, 44)

/stopsoundstopsound <targets> [<source>] [<sound>] — Stops a playing sound (or all sounds) for the targeted players. (Ch 30)

/summonsummon <entity> [<pos>] [<nbt>] — Summons a new entity at a position, with optional NBT data. (Ch 1, 2, 3, 4, 25, 27, 36)

/tagtag <targets> (add <name> | remove <name> | list) — Adds, removes, or lists scoreboard tags on entities. (Ch 3, 11, 13, 14, 16, 20, 27, 32, 33, 37, 43)

/teleport (alias /tp) — teleport [<targets>] (<location> [<rotation> | facing <facingLocation> | facing entity <facingEntity> [<facingAnchor>]] | <destination>) — Teleports entities to a location or to another entity. (Ch 1, 3, 4, 25, 27, 44, 45)

/tellrawtellraw <targets> <message> — Sends a formatted text-component (raw JSON text) message to players. (Ch 1, 5, 26, 30, 33, 34, 39)

/testtest (run <tests> ... | runclosest|runthat|runthese ... | runmultiple <tests> [<amount>] | runfailed ... | create <id> [<width>] [<height> <depth>] | locate <tests> | pos [<var>] | verify <tests> | clearall [<radius>]|clearthat|clearthese | resetclosest|resetthat|resetthese | export ... | stop) — Manages and runs GameTests. (Ch 8, 10, 14, 17, 25, 37, 39, 42, 43)

/titletitle <targets> ((title|subtitle|actionbar) <title> | times <fadeIn> <stay> <fadeOut> | clear | reset) — Controls the large screen text (title, subtitle, action bar) shown to players. (Ch 1, 5, 11, 19, 26, 32, 33, 39)

/versionversion — Prints the server’s game version info to chat (game name, pack_resource and pack_data formats, etc.). (Ch 9, 46)


Notes

  • tp is the built-in alias of teleport; both run the same command. It lives under Commands/teleport (there is no separate Commands/tp page), so the syntax above is the authoritative one.
  • gamerule is used as a command (e.g. /gamerule keepInventory true) and is also discussed where world settings come up; its syntax line traces to Commands/gamerule.
  • Long branching commands (/execute, /scoreboard, /data, /item, /loot, /test) are summarized to their top-level forms here. The full syntax trees are in the body chapters and on each command’s wiki page.
  • Every command the book uses is covered here: all 34 distinct commands (35 counting the tp alias) have a Commands/<name> page on the wiki if you want to dig deeper.

Appendix B — Pack Format Version Table

Every data pack carries a number that tells Minecraft which versions of the game it was built for. That number is the pack format. This appendix explains how the pack format works, how to declare it in your pack.mcmeta, and, most importantly, how to look up the right number for whatever version of Minecraft you are running.


What the pack format is

The pack format (sometimes called the pack version) is a number used in pack.mcmeta to describe which Java versions a data pack (or resource pack) is compatible with. Each release of Minecraft declares the pack formats it supports.

Its job is compatibility checking. When you load a pack, Minecraft compares the pack’s format number against the numbers the running game supports. If a pack’s format is higher than the game supports, the pack shows up as “incompatible” in the pack list. This is what stops a pack written for a newer version from loading into an older game and causing crashes or missing content. If the number does not match what the game expects, Minecraft displays a warning in the pack list.

Data packs and resource packs share the same numbering system but apply it differently. Data packs cover gameplay logic: structures, recipes, tags, loot tables, and functions. Resource packs cover textures, models, block states, sounds, and text. The numbers are not interchangeable between the two: a data pack format of 88 and a resource pack format of 88 mean different things. This appendix is about data pack formats.


How to declare it: min_format and max_format

The way a pack states its format changed partway through the modern release line, so there are two styles you may see. Knowing both lets you read any pack you come across.

The modern style (since snapshot 25w31a)

Since 25w31a, packs declare a range using two fields instead of a single number:

  • min_format — the minimum version the pack supports.
  • max_format — the maximum version the pack supports.

Each of these can be written as a single integer or as a list of two integers [major, minor], where the first number is the major (pack) version and the second is a minor version. A single integer is interpreted as that major version: 82, [82], and [82, 0] all mean the same thing. For max_format, a single integer is interpreted as any minor version of that major.

A modern data pack for Minecraft 1.21.9 and newer looks like this:

{
  "pack": {
    "description": "Example datapack for Minecraft 1.21.9",
    "min_format": 88,
    "max_format": 88
  }
}

Setting min_format and max_format to the same number, as above, means “this pack targets exactly this one major version.”

The legacy style: pack_format and supported_formats

Before 25w31a, a pack declared its version with a single pack_format integer:

{
  "pack": {
    "description": "Example datapack for Minecraft 1.21",
    "pack_format": 48
  }
}

You will still see pack_format in older packs and tutorials. There was also an older multi-version field, supported_formats, which listed the major versions a pack supported. It accepted a single integer (42), a list ([42, 45]), or an inclusive range object ({ "min_inclusive": 42, "max_inclusive": 45 }).

Both pack_format and supported_formats are now backwards-compatibility fields only. They exist so a pack can also support old versions of the game. The rule the format spec states is precise:

  • These deprecated fields must be present if the pack supports old versions (data pack format below 82).
  • They must be absent if the pack does not support old versions.

In other words: if you are writing a pack only for current Minecraft, do not add pack_format or supported_formats: use min_format/max_format alone. Add the legacy fields only when you are deliberately shipping one pack that also works on pre-82 versions.

The same min_format / max_format (or legacy formats) mechanism also appears on each entry in a pack’s overlays, which let one pack ship different contents for different version ranges. Overlays are an advanced topic; the version-declaration rules there are the same ones described above.


Finding the pack-format number for your version

This is the part that actually matters when you sit down to write a pack: you look the number up from the game itself rather than memorizing it. There are two built-in ways to read the pack format of the version you are running:

  1. The /version command — run it in-game (it requires cheats).
  2. The F3 + V debug hotkey.

Either one reports the pack format the running game supports. Whatever number it gives you is the number to put in your pack.mcmeta.

One concrete worked value

As a worked example you can check against, the data pack format for Minecraft 1.21.9 and newer is 88. The modern pack.mcmeta shown earlier uses it: "min_format": 88, "max_format": 88.

Looking up any other version’s number. Format numbers change with almost every release, so there’s no point memorizing a full table, and the printed one would be out of date by the time you read it. To get the exact number for your version, read it off the game itself: run /version or press F3 + V. For the complete history of every format number and the version it maps to, the Minecraft Wiki’s Pack format page keeps the full table current.


Quick reference

FieldStyleValue formWhen to use
min_formatmodern (25w31a+)int or [major, minor]Always, on current packs
max_formatmodern (25w31a+)int or [major, minor]Always, on current packs
pack_formatlegacysingle intBack-compat only; omit on new-only packs
supported_formatslegacyint, [a, b], or {min_inclusive, max_inclusive}Back-compat only; omit on new-only packs
  • A concrete data pack format to check against: 88 (Minecraft 1.21.9+).
  • To read your version’s number: /version or F3 + V.
  • For the full number → version table: look it up live on the Minecraft Wiki’s Pack format page.

Appendix C — All Data Component Types

Every data component the game recognises, grouped by what it does. For each one you get a one-line description and a note on the value it expects. Where a component is taught in the book, the chapter is marked like (Ch23). Components without a chapter marker are not taught directly but follow the same item_id[component=value] bracket syntax you learned in Chapter 21.

This list comes from the wiki’s Data component format page (its master List of components). It reflects current Java Edition (26.2). A component’s value is always written in SNBT (a single value, a list [...], or a compound {...}) exactly as in the /give examples throughout Part VI. The “value note” below describes the typical shape; for the full field list of any component, look up its entry on the wiki’s Data component format page.

A handful of components are non-encoded (used internally, never written on items in commands) or joke-version only (April Fools’ snapshots). Those are listed last, for completeness, and are out of scope for normal data packs.


Display components

These shape how an item looks, what it’s called, and what its tooltip shows. They never change behaviour. Most are taught in Chapter 22.

  • minecraft:custom_name — A player-set custom name for an item, block, or entity; can be added, changed, or removed by anyone with the item at an anvil. Value: a text component (e.g. {text:"Magic Wand",color:"light_purple",italic:false}). (Ch21, Ch22)
  • minecraft:item_name — A built-in name the item carries that, unlike custom_name, cannot be changed in an anvil and is shown in italics-off. Value: a text component or string. (Ch22)
  • minecraft:lore — Extra description lines shown under the name in the tooltip. Value: a list of text components, one per line (e.g. [{text:"This Stick is very sticky."}]). (Ch22)
  • minecraft:rarity — The item’s rarity, which colours its name. Value: a rarity keyword (common, uncommon, rare, epic). (Ch22)
  • minecraft:enchantment_glint_override — Forces the shimmer “glint” on or off, overriding the default. Value: boolean (true/false). (Ch21, Ch22)
  • minecraft:item_model — Makes the item render using a different item’s model. Value: a resource location (e.g. "minecraft:diamond_sword"). (Ch22)
  • minecraft:custom_model_data — Arbitrary data (floats, strings, colors, flags) a resource-pack model can read to choose a custom appearance. Value: a compound of typed lists. (Ch22)
  • minecraft:tooltip_style — Selects a custom tooltip box style for the item. Value: a resource location. (Ch22)
  • minecraft:tooltip_display — Hides the whole tooltip, or hides specific components from it. Value: a compound (e.g. {hidden_components:["minecraft:enchantments"]} or {hide_tooltip:1b}). (Ch22)
  • minecraft:map_color — Tints the filled-map item texture. Value: a decimal RGB color. (Ch24)
  • minecraft:map_decorations — Markers/icons placed on a map. Value: a compound of named decorations. (Ch24)
  • minecraft:map_id — Links a filled-map item to its stored map data. Value: an integer map ID. (Ch24)
  • minecraft:dye — Marks a dye item and which color it acts as. Value: a color keyword (e.g. "red").
  • minecraft:dyed_color — The dyed tint of leather armor and similar items. Value: a decimal/hex RGB color or [r,g,b] float list.
  • minecraft:trim — Armor trim pattern and material applied to a piece of armor. Value: a compound {pattern:..., material:...}. (Ch24)
  • minecraft:banner_patterns — The list of layered patterns on a banner. Value: a list of {pattern,color} compounds. (Ch24)
  • minecraft:base_color — The base color of a shield’s banner. Value: a color keyword (e.g. "lime"). (Ch24)
  • minecraft:pot_decorations — The sherds on each face of a decorated pot. Value: a list of sherd item IDs. (Ch24)
  • minecraft:profile — The player skin shown on a player head. Value: a player name, UUID, or full profile compound. (Ch24)
  • minecraft:note_block_sound — Overrides the note-block sound a placed player head triggers. Value: a sound event ID.
  • minecraft:firework_explosion — A single firework-star explosion shape and colors. Value: an explosion compound. (Ch24)

Functional components

These change how an item behaves: eating, mining, fighting, wearing, durability, cooldowns. Most are taught in Chapter 23; durability/repair details also appear there.

  • minecraft:food — Makes the item edible and sets nutrition/saturation. Value: a compound (e.g. {nutrition:3,saturation:1,can_always_eat:true}). (Ch23)
  • minecraft:consumable — Lets the item be consumed and configures consume time, animation, sound, and on-consume effects. Value: a compound (e.g. {consume_seconds:3.0,animation:'eat',...}). (Ch23)
  • minecraft:use_remainder — The item left behind after the item is used up (like a bucket → empty bucket). Value: an item stack compound ({id:..., count:..., components:...}). (Ch24)
  • minecraft:use_cooldown — A cooldown applied after use, optionally shared across a cooldown group. Value: a compound (e.g. {seconds:10,cooldown_group:"foo:bar"}). (Ch23)
  • minecraft:use_effects — Effects applied when the item is used. Value: a compound of effect definitions.
  • minecraft:tool — Makes the item act as a tool: mining speed and per-block rules. Value: a compound (e.g. {default_mining_speed:1.5,rules:[...]}). (Ch23)
  • minecraft:weapon — Makes the item act as a weapon, with durability-per-attack and shield-disable fields (attack damage comes from attribute_modifiers). Value: a compound (e.g. {item_damage_per_attack:10,disable_blocking_for_seconds:5}). (Ch23)
  • minecraft:attack_range — How far the item’s melee hit reaches, in blocks. Value: a compound. (Ch23)
  • minecraft:minimum_attack_charge — How full the attack indicator must be before the item can attack. Value: a float 0–1 (e.g. 0.5).
  • minecraft:swing_animation — Customises the item’s swing animation. Value: a compound.
  • minecraft:piercing_weapon — Makes a melee attack pierce through targets, with custom sounds. Value: a compound (e.g. {sound:..., hit_sound:...}).
  • minecraft:kinetic_weapon — A charge/ram attack with timing windows for damage, knockback, and dismount. Value: a compound of timing fields. (Ch23, new in 26.x)
  • minecraft:blocks_attacks — Turns the item into a shield, defining damage reductions, disable behaviour, and block sound. Value: a compound (e.g. {damage_reductions:[...],block_sound:...}). (Ch23, new in 26.x)
  • minecraft:weapon — see above.
  • minecraft:damage — Current damage taken by a damageable item (how worn it is). Value: an integer. (Ch21, Ch23)
  • minecraft:max_damage — Maximum durability before the item breaks. Value: an integer (e.g. 4). (Ch23)
  • minecraft:break_sound — The sound played when the item runs out of durability and breaks. Value: a sound event ID (e.g. "item.wolf_armor.break"). (new in 26.x)
  • minecraft:damage_resistant — Item types of damage the item entity is immune to (e.g. fire). Value: a compound (e.g. {types:"#minecraft:is_fire"}). (Ch24)
  • minecraft:unbreakable — Marks the item as never losing durability. Value: an empty compound {} (with optional fields). (Ch23)
  • minecraft:repairable — Which items can repair this one in an anvil. Value: a compound (e.g. {items:"stick"}). (Ch23)
  • minecraft:repair_cost — The accumulated anvil prior-work penalty. Value: an integer. (Ch23)
  • minecraft:enchantable — Makes the item enchantable in an enchanting table and sets its enchantability. Value: a compound (e.g. {value:15}). (Ch23)
  • minecraft:enchantments — The active enchantments on the item. Value: a compound mapping enchantment IDs to levels (e.g. {sharpness:3,knockback:2}). (Ch21, Ch24)
  • minecraft:stored_enchantments — Inactive enchantments stored (as on an enchanted book) that don’t apply until transferred. Value: a compound of enchantment→level. (Ch24)
  • minecraft:equippable — Makes the item wearable in a given slot, with equip sound and worn asset. Value: a compound (e.g. {slot:"head",equip_sound:...}). (Ch23)
  • minecraft:glider — Lets the item function as elytra wings when equipped. Value: an empty compound {}. (Ch23, new in 26.x)
  • minecraft:death_protection — Saves the holder from death (totem behaviour) with custom on-death effects. Value: a compound (e.g. {death_effects:[...]}). (Ch23, new in 26.x)
  • minecraft:attribute_modifiers — Attribute modifiers the item applies (e.g. scale, attack damage, movement). Value: a list of modifier compounds. (Ch23)
  • minecraft:max_stack_size — Overrides how many of the item stack in one slot. Value: an integer (1–99, e.g. 64). (Ch23)
  • minecraft:rarity — see Display; also affects enchanting glint behaviour. (Ch22)
  • minecraft:can_break — In Adventure mode, the blocks this item is allowed to break. Value: a compound listing block IDs/tags.
  • minecraft:can_place_on — In Adventure mode, the blocks this block-item may be placed against. Value: a compound listing block IDs/tags.
  • minecraft:intangible_projectile — Marks a fired projectile that can’t be picked up in Survival. Value: a compound.
  • minecraft:damage_type — Overrides the damage type the item deals on hit. Value: a damage-type ID (e.g. "minecraft:campfire").
  • minecraft:potion_duration_scale — Scales the duration of effects from the item’s potion contents. Value: a float multiplier (e.g. 2). (Ch24)
  • minecraft:ominous_bottle_amplifier — The Bad Omen amplifier granted by an ominous bottle. Value: an integer.
  • minecraft:recipes — Recipes unlocked when a knowledge book is used. Value: a list of recipe IDs.
  • minecraft:jukebox_playable — Lets the item play a music disc in a jukebox. Value: a song/jukebox-song reference. (Ch24)
  • minecraft:instrument — The instrument a goat horn plays. Value: an instrument ID or compound. (Ch24)
  • minecraft:provides_banner_patterns — A banner-pattern item: which pattern tag it can apply to a banner. Value: a banner-pattern tag (e.g. '#minecraft:pattern_item/globe'). (new in 26.x)
  • minecraft:provides_trim_material — A trim-material item: which trim material it supplies in smithing. Value: a trim-material reference. (new in 26.x)
  • minecraft:lock — Locks a container so it opens only while holding a matching key item. Value: a key-predicate compound. (Ch24)
  • minecraft:lodestone_tracker — Points a compass at a tracked lodestone position/dimension. Value: a compound (target pos + dimension). (Ch24)

Container components

These let one item hold other items or item-like contents.

  • minecraft:container — The items stored inside a container item (shulker box, barrel, etc.). Value: a list of {slot,item} compounds. (Ch24)
  • minecraft:container_loot — A loot table that fills the container when first opened. Value: a compound (e.g. {loot_table:"chests/desert_pyramid"}). (Ch24)
  • minecraft:bundle_contents — The items packed inside a bundle. Value: a list of item stack compounds. (Ch24)
  • minecraft:charged_projectiles — Projectiles currently loaded in a crossbow. Value: a list of item stack compounds. (Ch24)
  • minecraft:bees — Bees currently housed in a bee nest/hive item. Value: a list of bee compounds (entity_data + hive timers). (Ch24)
  • minecraft:sulfur_cube_content — The contents of a sulfur-cube item. Value: a compound. (new in 26.x)

Specialty components

Niche or block/entity-data components: written books, potions, maps, NBT carried into placed blocks and spawned entities, your pack’s private scratchpad, and the mob-customising components.

  • minecraft:custom_data — A private scratchpad of arbitrary NBT your data pack can read and match on; the game ignores it. Value: any compound (e.g. {foo:1}). (Ch21, Ch24)
  • minecraft:writable_book_content — The editable pages of a book-and-quill. Value: a compound with a pages list. (Ch24)
  • minecraft:written_book_content — The signed pages, title, and author of a written book. Value: a compound. (Ch24)
  • minecraft:potion_contents — The potion type and/or custom effects an item brews. Value: a compound with potion (string ID), custom_effects (list), custom_color (int — a decimal color overriding the swirl/particle color), and custom_name (string). (Ch24)
  • minecraft:suspicious_stew_effects — The effects a suspicious stew grants when eaten. Value: a list of effect compounds. (Ch24)
  • minecraft:fireworks — A firework rocket’s flight duration and explosion list. Value: a compound. (Ch24)
  • minecraft:block_state — Block-state properties applied when the block item is placed. Value: a compound of property→value (e.g. {type:"top"}). (Ch24)
  • minecraft:block_entity_data — NBT copied into the block entity when the item is placed (e.g. a spawner’s contents). Value: a block-entity compound. (Ch24)
  • minecraft:entity_data — NBT applied to the entity an item spawns/places (e.g. an armor stand). Value: an entity compound including its id. (Ch24)
  • minecraft:bucket_entity_data — NBT of the creature stored in a mob bucket. Value: an entity compound (name/variant live in their own components).
  • minecraft:debug_stick_state — Which block-state property the debug stick is set to edit per block. Value: a compound of block ID → property.
  • minecraft:firework_explosion — see Display (firework-star shape).

Mob-customising components (entity variant components)

Present on spawn eggs, mob buckets, paintings, item frames, etc. They set the variant, color, size, or collar of the entity the item produces. All take a single keyword value (in quotes), and most are covered together in Chapter 24.

  • minecraft:axolotl/variant — Axolotl color variant. Value: a variant keyword (e.g. "blue"). (Ch24)
  • minecraft:cat/variant — Cat appearance variant. Value: a variant keyword (e.g. "jellie"). (Ch24)
  • minecraft:cat/collar — Tamed-cat collar color. Value: a color keyword (e.g. "blue"). (Ch24)
  • minecraft:chicken/variant — Chicken variant (also affects what an egg hatches). Value: a variant keyword (e.g. "cold"). (Ch24)
  • minecraft:cow/variant — Cow variant. Value: a variant keyword (e.g. "cold"). (Ch24)
  • minecraft:fox/variant — Fox variant. Value: a variant keyword (e.g. "snow"). (Ch24)
  • minecraft:frog/variant — Frog variant. Value: a variant keyword (e.g. "cold"). (Ch24)
  • minecraft:horse/variant — Horse coat variant. Value: a variant keyword (e.g. "chestnut"). (Ch24)
  • minecraft:llama/variant — Llama variant. Value: a variant keyword (e.g. "gray"). (Ch24)
  • minecraft:mooshroom/variant — Mooshroom variant. Value: a variant keyword (e.g. "brown"). (Ch24)
  • minecraft:painting/variant — Which painting motif is placed. Value: a painting-variant keyword (e.g. "plant"). (Ch24)
  • minecraft:parrot/variant — Parrot color variant. Value: a variant keyword (e.g. "blue"). (Ch24)
  • minecraft:pig/variant — Pig variant. Value: a variant keyword (e.g. "warm"). (Ch24)
  • minecraft:rabbit/variant — Rabbit variant. Value: a variant keyword (e.g. "evil"). (Ch24)
  • minecraft:salmon/size — Salmon size. Value: a size keyword (e.g. "large"). (Ch24)
  • minecraft:sheep/color — Sheep wool color. Value: a color keyword (e.g. "blue"). (Ch24)
  • minecraft:shulker/color — Shulker color. Value: a color keyword (e.g. "red"). (Ch24)
  • minecraft:tropical_fish/base_color — Tropical fish base color. Value: a color keyword. (Ch24)
  • minecraft:tropical_fish/pattern — Tropical fish pattern. Value: a pattern keyword (e.g. "snooper"). (Ch24)
  • minecraft:tropical_fish/pattern_color — Tropical fish pattern color. Value: a color keyword. (Ch24)
  • minecraft:villager/variant — Villager biome type. Value: a variant keyword (e.g. "desert"). (Ch24)
  • minecraft:wolf/variant — Wolf variant. Value: a variant keyword (e.g. "rusty"). (Ch24)
  • minecraft:wolf/collar — Tamed-wolf collar color. Value: a color keyword (e.g. "blue"). (Ch24)
  • minecraft:wolf/sound_variant — Wolf sound variant. Value: a variant keyword (e.g. "cute"). (Ch24)

Components you won’t write in commands

The following exist in the game but are not part of the normal item_id[component=value] workflow. They are listed here only so the table is complete; you don’t use them in data packs.

Non-encoded (internal only)

Used by the game internally and never stored on items in commands; they cannot be set with /give or read with /data.

  • minecraft:additional_trade_cost — Added to a villager trade’s wanted count. Value: an integer.
  • minecraft:creative_slot_lock — Locks an informational paper item into its creative-inventory slot. Value: a compound.
  • minecraft:map_post_processing — Internal flag set when a filled map is locked or scaled. Value: an integer (0 lock / 1 scale).

Joke-version only (April Fools’ snapshots)

Added in joke snapshots; not present in normal releases.

minecraft:clicks, minecraft:contacts_messages, minecraft:explicit_foil, minecraft:fletching, minecraft:heat, minecraft:hovered, minecraft:lubrication, minecraft:potato_bane, minecraft:resin, minecraft:secret_message, minecraft:snek, minecraft:undercover_id, minecraft:views, minecraft:xp, minecraft:dimension_id, minecraft:exchange_value, minecraft:instant_room, minecraft:mine_active, minecraft:mine_completed, minecraft:mob_trophy/type, minecraft:sky, minecraft:special_mine, minecraft:trophy/type, minecraft:world_effect_uhint, minecraft:world_effect_unlock, minecraft:world_modifiers, minecraft:follow.

Appendix D — Vanilla Tag Reference

Scope: current Java Edition (26.2).

A tag (also called a registry tag) groups many game elements (blocks, items, entity types, damage types, and more) under one name so commands, recipes, predicates, and loot tables can treat the whole group as a single category. You write a tag with a leading #, for example #minecraft:logs, which stands for every log block at once. Many of these tags are built into vanilla Minecraft: the game itself reads them to decide things like which blocks an axe mines faster or which mobs burn in daylight. Adding entries to those vanilla tags from your own data pack changes that behavior without touching the game’s code.

This appendix is a hand-picked reference of the most common and useful vanilla tags, grouped by registry. It is not the complete list (vanilla ships hundreds of tags), but it covers the ones you will reach for most often. Each entry shows the tag id (always in the minecraft namespace, so #minecraft:logs is the full id for logs) and a one-line description of what it groups or controls.

How to read these. A tag id like logs means the full reference #minecraft:logs. Some tags use a slash, like mineable/pickaxe (full id #minecraft:mineable/pickaxe). When a description says “special behavior,” the game is hardcoded to react to that tag, so editing it changes how Minecraft plays.


D.1 Block tags

Block tags group blocks. They are used in world generation, advancements, and #-prefixed block arguments in commands, and many of them drive special block behavior.

TagWhat it groups / does
logsAll log and stem blocks (used by tree growth, parrot AI, the punch-tree tutorial).
logs_that_burnLogs that are flammable (excludes crimson/warped stems).
planksAll plank blocks.
leavesAll leaf blocks; broken faster by swords/shears, lets features generate through.
saplingsAll sapling blocks.
flowersAll flowers (saplings near these can grow a bee nest).
small_flowersThe one-block flowers; bees attempt to pollinate them.
woolAll wool blocks; sheared faster, makes a note block play guitar.
wool_carpetsAll carpet blocks.
cropsAll crop blocks.
dirtThe dirt-family blocks.
sandSand blocks; cacti/sugar-cane support, turtle-egg hatching.
iceAll ice blocks.
snowSnow blocks; turn snowy-variant blocks below them snowy.
stairsAll stairs.
slabsAll slabs.
wallsAll walls; affects fence-gate in_wall state and pathfinding.
fencesAll fences; mobs pathfind around them, leads can attach.
fence_gatesAll fence gates.
doorsAll doors; mobs pathfind through them, replaced with air in zombie villages.
wooden_doorsWooden doors that mobs (villagers) can open.
trapdoorsAll trapdoors; mobs pathfind around them.
buttonsAll buttons.
pressure_platesAll pressure plates.
railsAll rails; minecarts can be placed/dispensed onto them.
bedsAll beds; villagers/cats sleep on them.
signsAll signs; not destroyed by flowing liquids.
bannersAll banners; using a filled map on one toggles a map marker.
candlesAll candles; can be lit with flint and steel.
campfiresAll campfires; can be lit/extinguished.
flower_potsAll flower pots.
climbableBlocks the player and mobs can climb (ladders, vines, scaffolding).
portalsPortal blocks; entities are not placed inside them when dismounting.
dragon_immuneBlocks the Ender Dragon cannot destroy.
wither_immuneBlocks the wither cannot destroy by moving into them.
infiniburn_overworldBlocks that burn forever in the Overworld.
infiniburn_netherBlocks that burn forever in the Nether.
infiniburn_endBlocks that burn forever in the End.
fireFire blocks; affect portal detection, mob pathfinding, douseable by potions.
beacon_base_blocksValid blocks for a beacon’s pyramid base.
enderman_holdableBlocks an enderman can pick up.
replaceableBlocks another block can be placed into (replacing it).
dampens_vibrationsBlocks that block sculk-sensor vibrations when placed between source and sensor.
guarded_by_piglinsBlocks that anger piglins when a player destroys them.
mineable/axeBlocks mined faster with an axe.
mineable/hoeBlocks mined faster with a hoe.
mineable/pickaxeBlocks mined faster with a pickaxe (and required to drop them with one).
mineable/shovelBlocks mined faster with a shovel.
needs_stone_toolBlocks that need at least a stone tool to drop.
needs_iron_toolBlocks that need at least an iron tool to drop.
needs_diamond_toolBlocks that need at least a diamond tool to drop.
sword_efficientBlocks that break faster with a sword.
smelts_to_glassBlocks that give glass when smelted.

(The block-tag set is far larger: there are spawn-surface tags such as animals_spawnable_on, per-wood log tags such as oak_logs/birch_logs, ore tags, and many world-gen replaceable tags. Browse the full vanilla data pack (see D.5) for the complete list.)


D.2 Item tags

Item tags group items. They let a recipe accept several different inputs, control creative-search by #, and drive many gameplay features (feeding mobs, fuel, repair materials).

TagWhat it groups / does
planksAll plank items; used by many wooden-item recipes; furnace fuel (300 ticks).
logsAll log items; fuel for furnace; used in campfire/smoker recipes.
logs_that_burnFlammable logs; used in the charcoal recipe.
leavesAll leaf items.
saplingsAll sapling items; furnace fuel (100 ticks).
woolAll wool items; fuel (100 ticks); used in the painting recipe.
wool_carpetsAll carpet items; fuel (67 ticks).
coalsCoal and charcoal; used in the campfire recipe.
swordsAll swords.
axesAll axes.
pickaxesAll pickaxes.
shovelsAll shovels.
hoesAll hoes.
arrowsItems that can be shot by bows and crossbows.
boatsAll boats; furnace fuel (1200 ticks).
chest_boatsAll boats with chests.
signsAll sign items; furnace fuel (200 ticks).
hanging_signsAll hanging-sign items; furnace fuel (200 ticks).
bedsAll bed items.
bannersAll banner items; furnace fuel (300 ticks).
candlesAll candle items; used to decide if a candle can be placed on a cake.
dyesAll dyes.
wooden_tool_materialsItems that repair wooden tools/shields in an anvil.
stone_tool_materialsItems used to craft and repair stone tools.
iron_tool_materialsItems that repair iron tools in an anvil.
gold_tool_materialsItems that repair golden tools in an anvil.
diamond_tool_materialsItems that repair diamond tools in an anvil.
netherite_tool_materialsItems that repair netherite tools in an anvil.
trimmable_armorArmor that can receive a smithing trim.
trim_materialsMaterials usable as a trim addition.
beacon_payment_itemsItems accepted in the beacon GUI to choose an effect.
piglin_lovedItems piglins seek out and treat as “loved” (gold).
piglin_safe_armorArmor that stops piglins attacking the wearer.
meatAll raw/cooked meat items.
fishesAll fish items; used by dolphins and the fishing stat.
eggsAll egg items; used in cake/pumpkin-pie recipes.
cow_foodItems that tempt and feed cows and mooshrooms.
wolf_foodItems that feed tamed wolves.
cat_foodItems that tame and feed cats.
non_flammable_woodWood items that cannot be used as furnace fuel.
dampens_vibrationsItems that don’t trigger vibrations when thrown.
enchantable/durabilityItems that can take durability enchantments.
enchantable/weaponItems enchantable as weapons.
enchantable/armorItems enchantable as armor.
enchantable/miningItems enchantable for mining.
enchantable/vanishingItems that can receive Curse of Vanishing.

(Item tags are numerous: there is a <mob>_food tag for nearly every breedable mob, a repairs_<material>_armor tag for each armor material, the full enchantable/* family, and per-wood tags. Browse the full vanilla data pack (see D.5) for the complete list.)


D.3 Entity type tags

Entity type tags group entity types. They work in the type= target selector argument and in loot-table conditions (with #), and they control many mob behaviors.

TagWhat it groups / does
skeletonsSkeleton-type mobs; used in the creeper loot table for the music-disc drop.
zombiesAll zombie mobs.
raidersRaid mobs; glow when a bell rings; used by the “Voluntary Exile” advancement.
illagerAll illager mobs.
undeadUndead mobs; armadillos roll up near them.
arrowsArrow projectiles; affected by Power and Punch.
arthropodAll arthropod mobs.
aquaticAll aquatic mobs.
sensitive_to_smiteMobs affected by the Smite enchantment.
sensitive_to_bane_of_arthropodsMobs affected by Bane of Arthropods.
sensitive_to_impalingMobs affected by the Impaling enchantment.
impact_projectilesProjectiles that break chorus flowers and decorated pots on hit.
redirectable_projectileProjectiles reflected by left-clicking them.
deflects_projectilesEntities that bounce projectiles off (e.g. breezes).
burn_in_daylightMobs that burn in direct sunlight.
can_breathe_under_waterEntities that cannot drown.
freeze_immune_entity_typesEntities that cannot be frozen.
freeze_hurts_extra_typesEntities that take 5× freezing damage.
fall_damage_immuneEntities that take no fall damage.
inverted_healing_and_harmEntities healed by Instant Damage and harmed by Instant Health.
frog_foodEntities frogs will eat.
powder_snow_walkable_mobsMobs that walk on powder snow without sinking.
wither_friendsEntities the wither will not target or hurt.
can_equip_saddleEntities that can wear a saddle.
dismounts_underwaterMounts that throw their rider when submerged.

(There are also many *_spawnable_on-style and AI-targeting tags. Browse the full vanilla data pack (see D.5) for the complete list.)


D.4 Damage type tags

Damage type tags group damage types. They are used in #-prefixed damage-type arguments and determine how various protections, game rules, and effects respond to a source of damage. These are the tags you edit when you want a custom damage source to behave like fire, fall, or explosion damage.

TagWhat it groups / does
bypasses_armorDamage that ignores armor reduction.
bypasses_shieldDamage that shields cannot block.
bypasses_invulnerabilityDamage that hurts even creative-mode invulnerability.
bypasses_effectsDamage that ignores all damage reduction (Resistance, enchantments).
bypasses_resistanceDamage that ignores the Resistance effect.
bypasses_enchantmentsDamage that ignores enchantment-based reduction.
bypasses_cooldownDamage that ignores the post-hit invincibility window.
bypasses_wolf_armorDamage not absorbed by wolf armor.
is_fireFire damage; ignored if fireDamage is off or with Fire Resistance; reduced by Fire Protection.
is_fallFall damage; ignored if fallDamage is off or with Slow Falling; reduced by Feather Falling.
is_drowningDrowning damage; ignored if drowningDamage is off or with Water Breathing.
is_freezingFreeze damage; ignored if freezeDamage is off or wearing leather.
is_explosionExplosion damage; reduced by Blast Protection.
is_projectileProjectile damage; reduced by Projectile Protection; used in many advancements.
is_lightningLightning damage; makes turtles drop bowls when killed by it.
is_player_attackPlayer-attack damage (not directly referenced by the game).
no_knockbackDamage that produces no knockback.
no_angerDamage that does not make a mob remember/anger at its attacker.
witch_resistant_toDamage reduced by 85% against witches.
wither_immune_toDamage the wither cannot take.
damages_helmetDamage that wears down the helmet.
always_kills_armor_standsDamage that destroys an armor stand in one hit.
mace_smashMace-smash damage; counts for the “Over-Overkill” advancement.

D.5 Getting the full vanilla data pack

This appendix lists the common vanilla tags, not all of them. To browse every tag the game ships with (and see exactly which blocks, items, or entities each one contains), you want the complete built-in (vanilla) data pack. One genuinely exists: Minecraft defines its many vanilla tags in the minecraft namespace inside a built-in data pack, and the game’s own features are built using that same data pack.

Getting your hands on it. The built-in data pack ships inside the game itself, not as a separate download. The most reliable way to read every vanilla tag is to run the game’s built-in data-and-asset generator (the “data generator” / reports), which writes the complete set of tag files to a folder you can open. The wiki’s Tag and Data pack pages are the place to look up the exact command for your version. Once generated, each tag lives at the same path you’d use in your own pack, so you can read it, copy it, or use it as a template.


Tags are grouped here by registry; the leading #minecraft: is implied for every id shown.

Appendix E — Advancement Trigger Reference

Every advancement criterion needs a trigger: the event that the game watches for. When the trigger fires and all of the criterion’s conditions pass, that criterion is marked complete. This appendix lists every trigger type the wiki documents for current Java Edition, with a short note on when it fires.

These are the values you put in a criterion’s "trigger" field, like this:

"criteria": {
  "my_criterion": {
    "trigger": "minecraft:consume_item",
    "conditions": {
      "item": { "items": "minecraft:golden_apple" }
    }
  }
}

The trigger decides when the game checks; the conditions decide whether the check passes. Triggers like minecraft:tick and minecraft:location fire constantly, so they are only useful with conditions attached. See Chapter 19 for how to build advancements that detect player events.

The triggers

  • minecraft:allay_drop_item_on_block — Triggers when an allay drops an item on a block.
  • minecraft:any_block_use — Triggers under the same conditions as both minecraft:default_block_use and minecraft:item_used_on_block.
  • minecraft:arbitrary_player_tick — Triggers every tick for one player only.
  • minecraft:avoid_vibration — Triggers when a vibration event is ignored because the source player is crouching.
  • minecraft:bee_nest_destroyed — Triggers when the player breaks a bee nest or beehive.
  • minecraft:bred_animals — Triggers after the player breeds 2 animals.
  • minecraft:brewed_potion — Triggers after the player takes any item out of a brewing stand.
  • minecraft:changed_dimension — Triggers after the player travels between two dimensions.
  • minecraft:channeled_lightning — Triggers after the player successfully uses the Channeling enchantment on an entity or a lightning rod.
  • minecraft:construct_beacon — Triggers after the player changes the structure of a beacon (when the beacon updates itself).
  • minecraft:consume_item — Triggers when the player consumes an item.
  • minecraft:crafter_recipe_crafted — Triggers when the player is ±8 blocks on any axis of a crafter when it crafts a recipe.
  • minecraft:cured_zombie_villager — Triggers when the player cures a zombie villager.
  • minecraft:default_block_use — Triggers when the player activates a block’s default use action (opening a door, pushing a button, opening a crafting grid, etc.).
  • minecraft:effects_changed — Triggers after the player gets a status effect applied or taken from them.
  • minecraft:enchanted_item — Triggers after the player enchants an item through an enchanting table (not through an anvil or commands).
  • minecraft:enter_block — Triggers once for each block the player’s hitbox is inside (up to 12 blocks), twice per tick plus once more for every time the player moves or looks around during the same tick. This can fire tens or even hundreds of times per tick.
  • minecraft:entity_hurt_player — Triggers after a player gets hurt (even when it’s not caused by an entity).
  • minecraft:entity_killed_player — Triggers after a living entity kills a player.
  • minecraft:fall_after_explosion — Triggers when a player lands after being launched upward by an explosion or wind burst.
  • minecraft:fall_from_height — Triggers when a player lands after falling.
  • minecraft:filled_bucket — Triggers after the player fills a bucket.
  • minecraft:fishing_rod_hooked — Triggers after the player successfully catches an item with a fishing rod or pulls an entity with a fishing rod.
  • minecraft:hero_of_the_village — Triggers when a raid ends in victory and the player has attacked at least one raider from that raid.
  • minecraft:impossible — Never triggers.
  • minecraft:inventory_changed — Triggers after any changes happen to the player’s inventory.
  • minecraft:item_delivered_to_player — Triggers when an allay delivers an item to the player.
  • minecraft:item_durability_changed — Triggers after any item in the inventory has been damaged in any form.
  • minecraft:item_used_on_block — Triggers when the player uses their hand or an item on a block (the item must be able to interact with the block).
  • minecraft:kill_mob_near_sculk_catalyst — Triggers after a player is the source of a mob or player being killed within the range of a sculk catalyst.
  • minecraft:killed_by_arrow — Triggers after the player kills a mob or player using an arrow in ranged combat.
  • minecraft:levitation — Triggers when the player has the levitation status effect.
  • minecraft:lightning_strike — Triggers when a lightning bolt disappears from the world, only for players within a 256-block radius of the lightning bolt.
  • minecraft:location — Triggers every 20 ticks (1 second).
  • minecraft:nether_travel — Triggers when the player travels to the Nether and then returns to the Overworld.
  • minecraft:placed_block — Triggers when the player places a block.
  • minecraft:player_damaged — Triggers when the player receives damage.
  • minecraft:player_generates_container_loot — Triggers when the player generates the contents of a container with a loot table set.
  • minecraft:player_hurt_entity — Triggers after the player hurts a mob or player.
  • minecraft:player_interacted_with_entity — Triggers when the player interacts with an entity.
  • minecraft:player_killed_entity — Triggers after a player is the source of a mob or player being killed.
  • minecraft:player_sheared_equipment — Triggers after a player shears equipment off of a mob, such as wolf armor.
  • minecraft:recipe_crafted — Triggers when the player crafts a recipe in a crafting table, stonecutter or smithing table.
  • minecraft:recipe_unlocked — Triggers after the player unlocks a recipe (using a knowledge book, for example).
  • minecraft:ride_entity_in_lava — Triggers when a player mounts an entity walking on lava and while the entity moves with them.
  • minecraft:safely_harvest_honey — Triggers when the player harvests honey from a bee nest/beehive with a campfire below it.
  • minecraft:shot_crossbow — Triggers when the player shoots a crossbow.
  • minecraft:slept_in_bed — Triggers when the player enters a bed.
  • minecraft:slide_down_block — Triggers when the player slides down a block.
  • minecraft:started_riding — Triggers when the player starts riding a vehicle, or an entity starts riding a vehicle currently ridden by the player.
  • minecraft:summoned_entity — Triggers after an entity has been summoned (works with iron golems, snow golems, copper golems, the ender dragon, and the wither; using dispensers, commands, or pistons still activates it).
  • minecraft:tame_animal — Triggers after the player tames an animal.
  • minecraft:target_hit — Triggers when the player shoots a target block.
  • minecraft:thrown_item_picked_up_by_entity — Triggers after the player throws an item and another entity picks it up.
  • minecraft:thrown_item_picked_up_by_player — Triggers when a player picks up an item thrown by another entity.
  • minecraft:tick — Triggers every tick (20 times a second).
  • minecraft:used_ender_eye — Triggers when the player uses an eye of ender (in a world where strongholds generate).
  • minecraft:used_totem — Triggers when the player uses a totem.
  • minecraft:using_item — Triggers for every tick that the player uses an item that is used continuously (bows, crossbows, honey bottles, milk buckets, potions, shields, spyglasses, tridents, food items, eyes of ender, etc.). Single-click items like fishing rods do not affect this trigger.
  • minecraft:villager_trade — Triggers after the player trades with a villager or a wandering trader.
  • minecraft:voluntary_exile — Triggers when the player causes a raid.

Appendix F — Loot Table Conditions and Functions Reference

A loot table is built from two kinds of building block you will reach for over and over: conditions that decide whether something happens, and functions that decide what the resulting item looks like. This appendix is a flat, scannable list of every condition type and every function type the current Java Edition documents, with a one-line summary of each. Use it as a lookup table once you already understand the ideas; the teaching versions live in Chapter 16 (your first loot table), Chapter 17 (functions and conditions in loot tables), and Chapter 37 (advanced loot tables).

A note on names. In a loot table the JSON key for a condition is "condition" and the key for a function is "function"; both take a resource location (the minecraft: namespace is omitted throughout this appendix). Two names changed in modern Minecraft and you will still see the old ones in older tutorials: the looting-drop function is now enchanted_count_increase (it was once called looting_enchant), and the NBT-copying function is now copy_custom_data (it was once called copy_nbt). The names in this appendix are the current ones the game actually accepts.


Conditions

In a loot table, conditions are predicates: the same “if this, then that” checks used in predicate files, target selectors, and execute if predicate. A pool, an entry, or a whole table can carry a conditions list; every predicate in that list must pass. The condition types below are every value the predicate condition field accepts.

Condition typeWhat it checks
all_ofPasses if all of a list of sub-predicates pass. Invokable from any context.
any_ofPasses if any one of a list of sub-predicates passes. Invokable from any context.
block_state_propertyChecks the mined block and its block states. Requires a block state from loot context; fails if not provided.
damage_source_propertiesChecks properties of the damage source. Requires an origin and a damage source from loot context; fails if not provided.
enchantment_active_checkChecks whether an enchantment is currently active. Requires enchantment-active status from loot context; usable only from the enchanted_location context.
entity_propertiesChecks properties of an entity (uses the same predicate structure as advancements). Invokable from any context.
entity_scoresChecks an entity’s scoreboard scores; all listed scores must pass. Requires the specified entity from loot context; fails if not provided.
invertedInverts (negates) another condition. Invokable from any context.
killed_by_playerPasses if an attacking_player entity is provided by loot context; fails if not provided.
location_checkChecks the current location (with optional X/Y/Z offsets) against location criteria. Requires an origin from loot context; fails if not provided.
match_toolChecks the tool used to mine the block (same item-predicate structure as advancements). Requires a tool from loot context; fails if not provided.
random_chanceGenerates a random number 0.0–1.0 and passes if it is below the given chance. Invokable from any context.
random_chance_with_enchanted_bonusLike random_chance, but the success rate scales with the level of a given enchantment on the attacker. Treats a missing enchantment as level 0.
referenceInvokes a separate predicate file and returns its result. Invokable from any context; cannot be used in enchantment definitions.
survives_explosionPasses with probability 1 ÷ explosion radius. Requires an explosion radius from loot context; always passes if not provided.
table_bonusPasses with a probability picked from a list indexed by enchantment power. Requires a tool from loot context; treats a missing tool as level 0.
time_checkCompares the current day time against a value or range, with an optional period modulo. Invokable from any context.
value_checkCompares a number (from a number provider) against another number or range. Invokable from any context.
weather_checkChecks the current weather (raining and/or thundering). Invokable from any context.

19 condition types.


Functions

Functions (also called item functions or loot functions) modify the item stacks a table produces: changing the count, adding components, enchanting, copying data, and so on. A function carries a function field naming its type, an optional conditions list, and its own type-specific parameters. The same function types are what an item modifier file contains, so this list doubles as the item-modifier reference. The types below are every value the function field accepts.

Function typeWhat it does
apply_bonusApplies a predefined bonus formula (binomial_with_bonus_count, uniform_bonus_count, or ore_drops) to the stack count, based on an enchantment level.
copy_componentsCopies components from a loot-context source (block entity, attacker, tool, etc.) onto the item, with optional include/exclude lists.
copy_custom_dataCopies NBT from an entity, block entity, or command storage into the item’s custom_data component. (Modern name; replaces legacy copy_nbt.)
copy_nameCopies an entity or block entity’s name tag into the item’s custom_name component.
copy_stateCopies block-state properties from loot context into the item’s block_state component.
discardRemoves the item stack.
enchant_randomlyEnchants the item with one randomly chosen enchantment (a book becomes an enchanted book).
enchant_with_levelsEnchants the item at a specified enchantment level, like an enchanting table at that level (a book becomes an enchanted book).
enchanted_count_increaseAdjusts the stack size based on an enchantment level on the killer entity. (Modern name; replaces legacy looting_enchant.)
exploration_mapConverts an empty map into an explorer map pointing at a nearby generated structure. Requires an origin from loot context.
explosion_decayRemoves some items from the stack when an explosion radius is provided (each item has a 1/radius chance to be lost).
fill_player_headAdds the required tags to make a player head for a player from loot context.
filteredApplies an item predicate, then runs on_pass/on_fail modifiers depending on the result.
furnace_smeltSmelts the item as a furnace would, without changing its count.
limit_countClamps the count of every item stack to an exact limit or a min/max range.
modify_contentsApplies a function to every item inside an inventory component (bundle_contents, charged_projectiles, or container).
referenceCalls a separate item-modifier file (sub-functions) by name.
sequenceApplies a list of functions in order; does not check conditions (all listed functions always run).
set_attributesAdds attribute modifiers to the item.
set_banner_patternAdds or replaces a banner’s patterns.
set_book_coverSets the cover details (author, generation, title) of the written_book_content component.
set_componentsSets (or, with a ! prefix, removes) data components on the item.
set_contentsFills a container-style component with item stacks from a list of loot entries.
set_countSets the stack size (absolute, or relative with add).
set_custom_dataSets the item’s custom_data component by merging in supplied NBT.
set_custom_model_dataSets the custom_model_data component (floats, flags, strings, and colors).
set_damageSets the item’s damage/durability fraction (absolute, or relative with add).
set_enchantmentsModifies the item’s enchantments by level (a book becomes an enchanted book).
set_fireworksSets the fireworks component (explosions and flight duration).
set_firework_explosionSets the firework_explosion component (shape, colors, trail, twinkle).
set_instrumentSets the instrument tags to a random value from an instrument tag.
set_itemReplaces the item type without changing count or components.
set_loot_tableSets the loot table (and optional seed) a placed container block will use when opened.
set_loreAdds or changes the item’s lore lines, with append/insert/replace modes.
set_nameSets the item’s custom_name (or item_name) to a JSON text component.
set_ominous_bottle_amplifierSets the ominous_bottle_amplifier component from a number provider.
set_potionSets the potion_contents component to a given potion ID.
set_random_dyesSets the dyed_color component by selecting random dyes.
set_random_potionSets the potion_contents component to a randomly chosen potion.
set_stew_effectSets the status effects of a suspicious stew (fails on any other item).
set_writable_book_pagesManipulates the pages of the writable_book_content component.
set_written_book_pagesManipulates the pages of the written_book_content component.
toggle_tooltipsToggles which item tooltips are visible (attribute modifiers, enchantments, trim, etc.).

43 function types.


See also

  • Chapter 16 — your first loot table: pools, rolls, entries, and weight.
  • Chapter 17 — using functions and conditions inside a loot table.
  • Chapter 37 — advanced loot tables: composite entries, item modifiers, loot context, and reuse via reference.
  • Chapter 18 — predicate condition reference (the same condition types, in their predicate-file role).

Scope: current Java Edition. Every type and description above comes from Java Edition’s Item modifier and Predicate definitions, the same ones the Loot table format uses. If a type you expect is missing here, check the wiki’s Item modifier and Predicate pages for the latest additions.

Appendix G — Data Pack Folder Structure Cheat Sheet

A one-page map of every folder a data pack can contain. Use it to answer two questions fast: “Which folder does this file go in?” and “Is this folder required or optional?” Every folder and its one-line purpose comes straight from the wiki’s Data pack page. Folder names use the book’s singular convention (function, recipe, loot_table, never functions/recipes), exactly as the game expects them.

How a data pack is laid out

A data pack is a folder (or a .zip) with two things at its root, plus a data folder that holds everything else:

<data pack name>/
├── pack.mcmeta      ← REQUIRED. The only mandatory file. Metadata in JSON.
├── pack.png         ← optional. Icon shown next to the pack in the selection screen.
└── data/
    └── <namespace>/ ← your namespace (e.g. mypack). "minecraft" overrides vanilla.
        └── ... registry folders (below) ...

The rule that drives everything: a file at data/<namespace>/<registry>/<path>.json is loaded into the <registry> registry with the ID <namespace>:<path>. Both <registry> and <path> may contain slashes, which just create deeper sub-folders. Functions use .mcfunction instead of .json; structures use .nbt.

What’s required vs. optional: only pack.mcmeta is mandatory. The data folder, the namespace folder, and every registry folder below are optional: you create a folder only when you have a file to put in it. An empty data pack is just pack.mcmeta.

The experimental-settings mark (*)

A folder marked with * is an experimental-settings folder. Putting even one valid file inside any * folder marks the whole pack as “using experimental settings”: opening the world shows a warning screen, the world cannot be uploaded to Realms, and changes to those folders cannot be applied with /reload: you must exit and reopen the world (or reboot the server). Folders without a * are safe and /reload-friendly.

The complete tree

data/<namespace>/
├── function              .mcfunction files with lists of commands.
├── structure             .nbt files defining a saved structure of blocks.
├── tags/                 collections of things; each sub-folder is one tag type (.json).
│   ├── function          tags of functions.
│   └── <registry>        tags can be defined for any registry (see tag types).
│
│   ── all following folders hold .json files defining the content ──
│
├── advancement           definitions of advancements.
├── banner_pattern      * textures and names to use for banner patterns.
├── cat_variant         * textures and spawn conditions of cat variants.
├── chat_type           * formatting of chat messages.
├── chicken_variant     * textures and spawn conditions of chicken variants.
├── cow_variant         * textures and spawn conditions of cow variants.
├── damage_type         * attributes of damage and death messages.
├── dialog              * definitions of dialogs.
├── dimension           * biome layout and terrain of dimensions.
├── dimension_type      * properties of dimensions.
├── enchantment         * enchantment effects, supported items, level cost, etc.
├── enchantment_provider * selection of enchantments for specific uses.
├── frog_variant        * textures and spawn conditions of frog variants.
├── instrument          * instruments for goat horns.
├── item_modifier         loot functions used to modify items.
├── jukebox_song        * jukebox song definitions.
├── loot_table            loot from mobs, blocks, chests, etc.
├── painting_variant    * size and texture of paintings.
├── pig_variant         * textures and spawn conditions of pig variants.
├── predicate             tests for conditions based on position, mobs, etc.
├── recipe                recipes for crafting, smelting, etc.
├── sulfur_cube_archetype * defines Sulfur Cube archetypes.
├── test_environment    * groups GameTests with the right preconditions to run.
├── test_instance       * a test that can be run by the GameTest framework.
├── timeline            * specifies events and attributes by time of day.
├── trade_set           * a set of trades selected by villagers / wandering traders.
├── trial_spawner       * configuration of trial spawners.
├── trim_material       * colors, ingredients, and name of trim materials.
├── trim_pattern        * textures and name of trim patterns.
├── villager_trade      * trades of villagers and wandering traders.
├── wolf_sound_variant  * sound variants of wolves.
├── wolf_variant        * textures and spawn conditions of wolf variants.
├── world_clock         * clocks used to keep track of internal time.
├── zombie_nautilus_variant * textures and spawn conditions of zombie nautilus variants.
└── worldgen/           * world-generation registries:
    ├── biome             biome generation options, effects, etc.
    ├── configured_carver carver cave definitions.
    ├── configured_feature configuration of features.
    ├── density_function  math to calculate a value for each position in the world.
    ├── noise             size and amplitudes of a noise.
    ├── noise_settings    terrain shape (noise caves) and main terrain block types.
    ├── placed_feature    placement of features within a chunk.
    ├── processor_list    post-processing of blocks in structures.
    ├── structure         structure generation and allowed biomes.
    ├── structure_set     distribution of a set of structures within the world.
    ├── template_pool     a set of templates (structure files) for jigsaw structures.
    ├── world_preset      sets of dimensions selectable in the Create World screen.
    ├── flat_level_generator_preset  presets for the "Superflat" world type.
    └── multi_noise_biome_source_parameter_list  preset name for the multi-noise biome layout.

Quick reference: required vs. optional

PathRequired?Reloadable with /reload?
pack.mcmetaYes — the only mandatory filen/a
pack.pngNon/a
data/<namespace>/functionNoYes
data/<namespace>/structureNoYes
data/<namespace>/tags/...NoYes
data/<namespace>/loot_tableNoYes
data/<namespace>/recipeNoYes
data/<namespace>/advancementNoYes
data/<namespace>/item_modifierNoYes
data/<namespace>/predicateNoYes
any * folder (above)NoNo — exit/reopen world or reboot server

Reminder. tags is the one plural folder (it holds tag types, one sub-folder each). Everything inside it, and every other registry folder, is singular. If a folder you need isn’t in this tree, it isn’t a data pack folder: double-check the spelling against the list above.


Source: the Data pack page of the Minecraft Wiki (Java Edition 26.2), “Folder structure” section. Every folder name, purpose, and * mark is taken directly from that list.

Glossary

A plain-language reference to the terms used throughout this book, written for readers age 12 and up with no coding background. Terms are listed alphabetically.


Advancement — A built-in way the game guides players through Minecraft and hands out challenges to complete, similar to achievements; you can also make your own as JSON files in a data pack. Argument types — The categories of value a command is allowed to accept in each of its slots — for example a true/false flag, a number, a player, or a position. Each spot in a command expects a particular argument type. Assets — The folder (assets/<namespace>/...) inside a resource pack where the game looks for the pack’s files, such as textures, models, and sounds. Attribute — A numeric property of a player, mob, or armor stand, such as max health or movement speed. Each entity has a base value plus modifiers that adjust it to a final value. Biome (world generation) — A region of the world with its own geography, plants, mobs, temperature, and colors, chosen while the world is being generated. Data packs store biomes as JSON files that set things like rainfall and temperature. Biome definition — The JSON file (at data/<namespace>/worldgen/biome/<name>.json) that defines one biome: its has_precipitation, temperature, downfall, color effects (such as the required water_color), the carvers and features it generates, and its spawners. Because biomes are a dynamic registry, the file loads when a world is created or opened — not on /reload. Biome tag — A named group of biomes (a tag file under tags/worldgen/biome/). The game uses biome tags to control where structures generate, to set the spawn conditions of entities (including which animal variant spawns), and as a #namespace:tag biome argument in commands. Downfall — A biome’s humidity value from 0.0 to 1.0, used mainly for grass and foliage colors; above 0.85 a biome counts as “humid.” Separate from has_precipitation, which simply turns rain/snow on or off. Mob category (spawning) — One of the eight groups a biome’s spawners block sorts mobs into — monster, creature, ambient, water_creature, underground_water_creature, water_ambient, misc, or axolotls. Each spawn entry sets a mob type, a weight (how often), and a minCount/maxCount pack size. A category that is missing or empty spawns nothing. Mob-variant definition registry — One of the dynamic registries holding a mob’s appearance variants — cat_variant, chicken_variant, cow_variant, frog_variant, pig_variant, wolf_variant, wolf_sound_variant, zombie_nautilus_variant — each its own data-pack folder. The matching minecraft:<mob>/variant item component (Chapter 23) is a string naming one of these entries. For the exact fields inside a variant definition file, see the Minecraft Wiki. click_event — An optional part of a text component that makes something happen — like running a command or opening a link — when a player clicks the text. Command storage — A general-purpose, named place where commands can save and read NBT data without needing a block or entity to hold it. Each storage has a namespaced ID so different packs don’t clash. Components syntax [...] — In commands like /give, you write an item as item_id[component=value,...], listing its data components in square brackets right after the item’s ID. Putting ! in front of a component (item_id[!component]) removes it instead. Configured feature — A JSON file describing a single thing world generation can place — like a tree, an ore blob, or a patch of flowers — already set up with its options. Coordinates — Numbers marking a spot in the world. Absolute coordinates are exact; ~ (tilde) coordinates are measured relative to where the command runs; and ^ (caret) “local” coordinates are measured from the way the executor is facing (left, up, and forward). Criterion / trigger — A criterion is one named requirement an advancement watches for. Each criterion names a trigger — the in-game event that sets it off, such as picking up an item — plus optional extra conditions that must also be true. custom_data — A data component that stores extra key-value information the game itself ignores, so packs can stash their own tags on an item (for example custom_data={team:"red"}). custom_model_data — A data component holding values (numbers, flags, strings, colors) that an item model definition reads to decide which custom model or color to show for that item. Damage type — A JSON file in a data pack defining a kind of damage an entity can take, including its behavior and the death message shown when it kills something. Custom damage types are dealt with the /damage command. /damage — A command that deals a chosen amount of damage to entities using the game’s normal damage logic, so protections like fire resistance can still reduce or cancel it. Data component — A structured, named piece of data attached to something (most often an item) that stores information and defines behavior; each has a namespaced ID like minecraft:custom_name and a value. On items they are called item components. Data component predicate — A test that checks an item’s data components and returns pass or fail. It’s used in places like item modifiers, loot conditions, advancement criteria, and entity or block checks. Data pack — A folder or .zip file (containing a pack.mcmeta) full of data that configures Minecraft features such as advancements, dimensions, enchantments, loot tables, recipes, structures, and biomes. The vanilla game itself is defined with a built-in data pack. /data — A command that gets, merges, modifies, or removes the NBT data of a block entity, an entity, or command storage, using the instructions get, merge, modify, and remove. Density function — A JSON file holding a math expression that produces a number for any position in the world. The noise router uses these to shape terrain. Dialog — A pop-up window you must respond to that can show information and take input from the player, using things like text boxes, toggles, sliders, and buttons that run commands. Dimension — A parallel world inside a Minecraft world — its own separate 3D space (like the Overworld, Nether, and End) with its own generation, biomes, and structures. Dimension type — A JSON file that sets a dimension’s properties, such as its build-height limits and how much ambient light it has. Enchantment definition — A JSON file in a data pack describing one enchantment: which items it works on, its maximum level, its cost, and its effects. enchantment_provider — A data-pack folder holding ready-made selections of enchantments for specific uses. Entity tag — A named group of entity types, written with a leading #, so a command or condition can match any type in the group at once (for example in a selector’s type argument). /execute — A command that runs another command while changing who runs it, where and at what angle, testing conditions first, or storing the result. You chain helper pieces (subcommands) and finish with run. Fake player — A made-up name used as a scoreboard score holder that doesn’t belong to any real player, handy for storing global numbers. Feature flag — A switch that turns on experimental features for a world. Flags can only be enabled when the world is created, and a data pack that needs an unenabled flag won’t load. Function (.mcfunction) — A plain text file (ending in .mcfunction) in a data pack that lists commands, one per line and without the leading slash. Running the function carries out all those commands in order. Function tag (minecraft:load / minecraft:tick) — A named group of functions you can run together. Functions in minecraft:load run once when the world loads or packs reload; functions in minecraft:tick run every tick. Game rule — One of a set of adjustable on/off or value options for a single world, changeable from menus or with the /gamerule command. hover_event — An optional part of a text component that shows a tooltip (a small pop-up box) when a player hovers the mouse over the text. Identifier / resource location — A namespaced name in the form namespace:path that points to a game object (block, item, entity type, function, etc.) with no ambiguity. Also called a resource location or namespaced ID. Item model definition — A JSON file in a resource pack (assets/<namespace>/items) that tells the game which model to draw for an item, possibly choosing different models based on the item’s components or situation. Item modifier — A change (or list of changes) applied to an item stack — like setting its count or adding enchantments. It can be saved as its own JSON file or used inside a loot table. Jigsaw / Jigsaw Block — A technical block used to build large structures out of smaller saved sections, connecting them together piece by piece during generation. JSON — A simple text format of key-value pairs and lists used to store and share data. Minecraft uses JSON for many data-pack files like advancements, loot tables, tags, recipes, and predicates. Loot context — The set of input values available to a loot table, predicate, item modifier, or number provider — for example which entity or position is involved — used to check whether they make sense for a given situation. Loot function — A single change applied to an item that a loot table produces, such as setting its stack size or adding enchantments. Loot table — A JSON file that decides which items appear in a situation — what’s inside generated chests, what a broken block or killed mob drops, what you fish up, and so on. Macro / $(key) — A function line that starts with $ and pulls in values passed to the function. You write $(key) where a value should go, and the game swaps in that key’s value just before running the line. min_format / max_format — Fields in pack.mcmeta giving the lowest (min_format) and highest (max_format) pack version a pack supports, so the game can tell whether the pack fits your version. Each is a number or a [major, minor] pair. Mob variant — A data component recording which visual version a mob is (for example cat/variant storing a cat’s look). For the underlying variant definition files, see the Minecraft Wiki. Namespace — A labeled grouping for content (the part before the colon in namespace:path) that keeps names from two packs from clashing. If you don’t give one, it defaults to minecraft. NBT — “Named Binary Tag,” a tree-shaped data format Minecraft uses inside many save files. It’s built from tags, each with a type, a name, and a value. NBT path — A short text string that points at specific data inside an NBT tree, written as nodes separated by dots, where each node selects which child tags to grab. Noise router — A bundle of density functions used during terrain generation to shape land, place biomes, fill aquifers, run ore veins, and more. It’s part of a dimension’s noise settings. Number provider — The way a loot table supplies a number where one is needed (like how many times to roll a pool): either a fixed constant, or a min/max range the game picks randomly from. Pack format (legacy / pack_format) — An older single number in pack.mcmeta saying which Java version a pack was built for, used to check compatibility. It has been replaced by min_format and max_format. pack.mcmeta — The metadata file whose presence tells Minecraft that a folder or .zip is a real data pack or resource pack, and which holds version and description info. Pack overlay — An optional sub-pack applied on top of a pack’s normal contents for certain game versions. Each overlay has its own folder, and they’re applied in the order listed. Placed feature — A JSON file that decides where a configured feature actually gets placed in the world, using placement rules, and can be pointed to by biomes. Pool / entry / weight — A loot table holds pools; each pool is rolled a set number of times, and every roll picks one entry. An entry’s weight is a number setting how likely it is to be chosen — its share of the pool’s total weight. Predicate — A JSON file that checks conditions in the world and returns pass or fail, letting data packs write “if this, then that” logic without real code. It can be called by data packs or commands. /random — A command that produces a random whole number in a range you give (like 1..6), or manages the world’s random sequences. Raycasting — A technique built from /execute using anchored eyes, positioned, and local ^ ^ ^ coordinates to step a point forward along the line of sight. Recipe — A data-driven rule that lets players transform items and blocks: crafting, smelting, blasting, smoking, campfire cooking, stonecutting, and smithing all use it. Types include crafting_shaped, crafting_shapeless, crafting_transmute, crafting_dye, the hardcoded crafting_special_* recipes, smithing_transform, and smithing_trim. Registry — A catalog of game objects that share a kind of identifier. Most registries are internal, but some “dynamic” ones can have content added through data packs. /reload — A command that re-reads the current data packs so your latest edits take effect without leaving the world. Broken data is skipped and the previous working version is kept. Resource pack — A folder or .zip that customizes the game’s look and feel — textures, models, sounds, music, languages, and fonts — without changing any code. Often paired with a data pack. /return — A command used inside a function to stop it early and set the value it hands back to whatever called it. It can also cut off a forking /execute after the first branch. /schedule — A command that tells the game to run a function (or a function tag) later, after a set delay, with options to replace or add to an existing schedule. Scoreboard / objective — The command-driven system that tracks numbers for players and entities. An objective is one named counter on it (with a criterion saying what it tracks), and each holder’s value is a whole number. SNBT — “Stringified NBT,” the readable text form of NBT data used a lot in Java commands. Its top level is usually a compound: key-value pairs inside curly braces { }. Sounds.json — A resource-pack file that tells the game which sound files to play for each in-game sound event. Structure / Structure Block — A structure is a naturally generated formation you can find with /locate and place with /place. A structure block is the in-game block used to save, load, and generate structures by hand. Structure set — A JSON file that decides where structures appear during world generation; simply having the file makes those structures generate. sulfur_cube_archetype — A JSON file in a data pack defining how sulfur cubes behave, such as their attribute modifiers and whether they float in liquids. supported_formats — A pack.mcmeta field listing the major pack versions a pack supports — a single number, a list, or a min/max range — which must match the min_format and max_format values. Surface rule — A decision-tree of conditions that decides which block goes at each surface spot of the terrain (grass and dirt, badlands bands, deepslate, bedrock, and so on), used in a pack’s noise settings. Tag (registry) — A named group of game elements treated as one category, written with a leading #. For example #minecraft:logs stands for every log block at once, so a command or recipe using it applies to them all. Target selector — A shorthand for picking players or entities in commands without naming them: @p (nearest player), @a (all players), @e (all entities), @s (the executor). You add [...] arguments to filter the targets. /tellraw — A command that sends a formatted message — written as a text component, so you control color, styling, and click actions — to chosen players. Template pool — A group of structure pieces for jigsaw structures, stored as a data-pack file, from which pieces are randomly chosen during generation. Text component — The format Minecraft uses for rich, formatted text (historically called “raw JSON text”). A root component can hold child components, which inherit its formatting and interactivity. Tick — One step of the game loop. Minecraft normally runs 20 ticks per second (one every 0.05 seconds), and most timed actions are measured in ticks. /title — A command that shows big on-screen text: a large center title, an optional subtitle beneath it, and text on the action bar above the hotbar, with adjustable fade-in, stay, and fade-out times. UUID — A “Universally Unique Identifier,” a 128-bit number Minecraft uses to tell things apart, usually written as hyphenated hexadecimal in an 8-4-4-4-12 pattern. Villager trade / trade set — Trading is the mechanic for exchanging emeralds and items with villagers and wandering traders; a villager trade tag is a named group of such trades. World preset — A setting that decides which dimensions a new world will have (picked with the “World Type” button when creating a world) and can be customized by a data pack.