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.datinside thedatafolder of your world save, under a folder named for that namespace. You never touch this file by hand (the/datacommand 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.3bs: 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.3Lf: a float (a decimal number), e.g.3.2fd: 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
5becomes an int and5.0becomes 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 namedmypack:config.Difficulty: at the tag namedDifficulty(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 equals5b.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), whiledata 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-levelmergeabove, 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.” (insertneeds a position number;appendandprependdon’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 addItemsto 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 withfrom.
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
-
Add a “peaceful” setter. Write
mypack:set_peacefulthat setsSettings.difficultyto"peaceful", setsSettings.mob_health_multiplierto0.5f, and usesprependto add"no_hostile_spawns"to the front ofSettings.rules. Compare howprependdiffers from theappendyou used in the walkthrough. -
Remove a rule. Write a function that uses
data removeto delete the first rule with the pathSettings.rules[0], then runconfig_getto confirm the list got shorter. -
Count the rules. Recall that
data geton a list returns its length. Write a one-line function that runsdata get storage mypack:config Settings.rulesand read the number it returns in chat. -
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 theruleslist, then read the list back and confirm the new rule landed in the second slot (positions count from 0). -
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. Runconfig_getto 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) orDifficultywhen you actually stored it underSettings.difficulty. Rundata get storage mypack:configwith no path to dump everything, then read the real tag names back and fix your path. NBT tag names are case-sensitive, soSettingsandsettingsare two different boxes.
What Went Wrong? — appending to something that isn’t a list
append,prepend, andinsertonly 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 exampleSettings.rules, notSettings.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, because2and2.0fare different types of tag. When a value should be a decimal, add thef(float) ord(double) suffix, like2.0f. When you read data back withdata 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.