Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Chapter 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 whole number from −128 to 127), e.g. 3b
  • s: a short (a whole number from −32,768 to 32,767), e.g. 3s
  • (no letter): an int (a whole number up to about ±2.1 billion), e.g. 3
  • L: a long (a whole number up to about ±9.2 quintillion), 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.