Scoreboards, storage & NBT

The integer-only scoreboard system, command storage as a datapack database, the NBT/SNBT data model, and the execute store bridge and macros that tie them together β€” the numeric and structured-data backbone of Java-Edition datapacks (primary target 1.21+).

Scoreboard system

A scoreboard objective tracks a 32-bit signed integer score per score holder. Scores range from -2,147,483,648 to 2,147,483,647. A score holder is a player username, an entity UUID, or an arbitrary string called a fake player. Every objective has exactly one criterion that defines how the game may change it automatically β€” or dummy/trigger, which the game never touches on its own.

Objective names may contain alphanumerics plus -, +, ., _. Historically these were capped at 16 characters; modern versions are more lenient, but keep names short and machine-safe.

Full /scoreboard syntax

Objectives

scoreboard objectives list
scoreboard objectives add <objective> <criterion> [<displayName>]
scoreboard objectives remove <objective>
scoreboard objectives setdisplay <slot> [<objective>]        # omit objective to clear the slot
scoreboard objectives modify <objective> displayname <component>
scoreboard objectives modify <objective> rendertype (hearts|integer)
scoreboard objectives modify <objective> displayautoupdate <true|false>   # 1.20.3+
scoreboard objectives modify <objective> numberformat blank               # 1.20.3+
scoreboard objectives modify <objective> numberformat fixed <component>   # 1.20.3+
scoreboard objectives modify <objective> numberformat styled <style>      # 1.20.3+

Players (score holders)

scoreboard players list [<target>]
scoreboard players get <target> <objective>
scoreboard players set <targets> <objective> <score>
scoreboard players add <targets> <objective> <amount>
scoreboard players remove <targets> <objective> <amount>
scoreboard players reset <targets> [<objective>]         # reset removes the score entirely (not =0)
scoreboard players enable <targets> <objective>          # only meaningful for 'trigger' objectives
scoreboard players operation <targets> <targetObj> <op> <source> <sourceObj>
scoreboard players display name <targets> <objective> [<component>]                                  # per-holder display name
scoreboard players display numberformat <targets> <objective> (blank|fixed <c>|styled <style>)        # 1.20.3+

Operations

The operation subcommand takes an operator <op> that combines a target score with a source score.

OperatorMeaning
=Assign: target = source
+=Add: target = target + source
-=Subtract: target = target − source
*=Multiply: target = target × source
/=Divide: floor division (rounds toward −∞)
%=Modulo: floored modulo (result takes the sign of the divisor)
<Min: assign the smaller of the two
>Max: assign the larger of the two
><Swap: exchange the two scores
⚠ Gotcha

/= and %= use floor division / floored modulo β€” Minecraft rounds toward negative infinity. So -7 /= 2 gives -4 (not -3), and -1 %= 4 gives 3 (not -1). Handle signs explicitly if you need C-style truncation.

Criteria (Java Edition)

Every objective is created with one criterion. Single (non-statistic) criteria:

CriterionBehaviourRead/write
dummyNever changed by the game; only by commandscommands only
triggerChanged by the player via /trigger when enabledplayer via /trigger
deathCount+1 each time the holder diesauto
playerKillCount+1 each time the holder kills a playerauto
totalKillCount+1 each time the holder kills a player or mobauto
health0–20 half-hearts (can exceed 20 with absorption)read (auto)
xpTotal XP collected since last deathread (auto)
levelCurrent XP levelread (auto)
food0–20 hunger pointsread (auto)
air0–300 breath while underwaterread (auto)
armor0–20 armor pointsread (auto)

Team-color criteria β€” each has 16 color variants (black, dark_blue, … white):

CriterionBehaviour
teamkill.<color>+1 when the holder kills a member of team <color>
killedByTeam.<color>+1 when the holder is killed by a member of team <color>

Statistic (compound) criteria β€” any tracked statistic can be a live objective that increments automatically as the underlying statistic changes. The general form is:

minecraft.<category>:<namespaced statistic id>

Categories map to the statistics registry:

Category prefixMeaningExample criterion
minecraft.customCustom stats (jumps, playtime, distances…)minecraft.custom:minecraft.jump
minecraft.minedBlocks minedminecraft.mined:minecraft.stone
minecraft.usedItems usedminecraft.used:minecraft.carrot_on_a_stick
minecraft.craftedItems craftedminecraft.crafted:minecraft.bread
minecraft.brokenTools brokenminecraft.broken:minecraft.diamond_pickaxe
minecraft.picked_upItems picked upminecraft.picked_up:minecraft.emerald
minecraft.droppedItems droppedminecraft.dropped:minecraft.dirt
minecraft.killedSpecific mob killed by holderminecraft.killed:minecraft.zombie
minecraft.killed_byKilled by a specific mobminecraft.killed_by:minecraft.creeper

custom:<stat> is the shorthand often written for minecraft.custom:minecraft.<stat>. Because these objectives increment live, a datapack can detect the change and reset the score to act on it β€” the common "on right-click carrot-on-a-stick" detection pattern.

πŸ“Œ Note

The minecraft.<category>:<id> line above is the source-quoted naming pattern. The category list is illustrative of that pattern rather than an exhaustive registry snapshot β€” consult the current statistics registry for the complete, version-exact set of categories and statistic ids.

Display slots

Objectives are shown in a display slot via scoreboard objectives setdisplay <slot>:

Number formats & display names

Objectives (and per-holder scores) can restyle how the number renders 1.20.3+:

The <component> and <style> arguments are raw-JSON text components. For the full component grammar (colors, formatting, events), see Text components & raw JSON.

⚠ Version differences
  • Since 1.5 (13w04a): scoreboard system introduced.
  • Since 1.8 (14w06a): trigger criterion added; non-player entities became valid score holders (enables fake players / entity UUIDs).
  • Since 1.9 (15w32b): xp, food, air criteria added.
  • Since 1.13: displayname modify (1.13-pre7) and rendertype modify (1.13-pre8); display slots later renamed to underscore form.
  • Since 1.20.3 (23w46a): displayautoupdate and number formats (blank/fixed/styled) added, at both objective and per-holder level.

Examples

data/<namespace>/function/load.mcfunction β€” objective setup + a styled sidebar

# in a load function
scoreboard objectives add coins dummy {"text":"Coins","color":"gold"}
scoreboard objectives modify coins numberformat styled {"color":"yellow"}
scoreboard objectives setdisplay sidebar coins
scoreboard players set @a coins 0

data/<namespace>/function/detect_jump.mcfunction β€” detect a jump via a live stat criterion

scoreboard objectives add jumped minecraft.custom:minecraft.jump
# each tick: if a player's jump count changed, do something then reset
execute as @a[scores={jumped=1..}] run function ns:on_jump
scoreboard players reset @a jumped   # or 'set ... 0'; reset avoids leftover holders

The trigger criterion & /trigger

/trigger is the only score-changing command a non-operator (permission level 0) player may run β€” and only on objectives with the trigger criterion that have been enabled for that specific player. This makes it the standard mechanism for player-invokable actions (menus, "claim reward", vote buttons) in permission-locked survival/adventure worlds.

Full syntax

trigger <objective>              # +1
trigger <objective> add <value>  # +value
trigger <objective> set <value>  # = value

The enable/consume cycle

  1. Op/datapack creates the objective: scoreboard objectives add menu trigger.
  2. Enable it per player: scoreboard players enable @a menu. Until enabled, /trigger menu errors for that player.
  3. Player runs /trigger menu (or /trigger menu set 3). This auto-disables the objective for that player immediately after one use.
  4. A datapack polls the score, acts on it, resets it, and re-enables the trigger for the next use.

So each /trigger fires exactly once per enable β€” you must re-enable every cycle.

Complete pattern example

data/ns/function/menu/open.mcfunction β€” sends a clickable menu

tellraw @s ["",{"text":"[ Teleport to spawn ]","color":"aqua","clickEvent":{"action":"run_command","command":"/trigger menu set 1"}},"\n",{"text":"[ Get a kit ]","color":"green","clickEvent":{"action":"run_command","command":"/trigger menu set 2"}}]
scoreboard players enable @s menu

data/ns/function/menu/poll.mcfunction β€” runs every tick (from #minecraft:tick)

execute as @a[scores={menu=1}] at @s run function ns:menu/tp_spawn
execute as @a[scores={menu=2}] at @s run function ns:menu/give_kit
# consume + re-arm anyone who used it
execute as @a[scores={menu=1..}] run scoreboard players enable @s menu
scoreboard players reset @a menu
πŸ“Œ Note

clickEvent.action: "run_command" runs the command as the clicking player at their permission level. That is exactly why /trigger (level 0) works here while /scoreboard (level 2) would not.

πŸ’‘ Best practice
  • Always set/reset the score after handling, then enable again β€” a stuck non-zero score fires every tick.
  • Use set <n> (not add) from menus so each button maps to a distinct value regardless of prior state.
  • Keep the poll in #minecraft:tick, and keep the enable in the same poll so re-arming can't be forgotten.

Data storage & the /data command

/data reads and writes NBT on three target kinds:

Requires permission level 2 (Java-only command).

Full syntax

data get    (block <pos> | entity <target> | storage <id>) [<path>] [<scale>]
data merge  (block <pos> | entity <target> | storage <id>) <nbt compound>
data remove (block <pos> | entity <target> | storage <id>) <path>
data modify (block <pos> | entity <target> | storage <id>) <path> <operation> <source>

Modify operations

OperationEffect
setOverwrite the target path with the source value
mergeDeep-merge source compound into the target compound
appendAdd source to the end of a list
prependAdd source to the start of a list
insert <index>Insert source at list index (negative = from end)
remove(As a standalone subcommand) delete the value/element at the path

Modify sources

Used by set/merge/append/prepend/insert:

value <snbt>                                                                       # a literal NBT value
from   (block <pos>|entity <t>|storage <id>) [<sourcePath>]                         # copy a value
string (block <pos>|entity <t>|storage <id>) [<sourcePath>] [<start>] [<end>]       # 1.19.4+: substring a string

The get scale

data get ... <path> <scale> multiplies the numeric result by <scale>, then floors it. This reads a fractional NBT value into a printable/int-usable form (and historically was used to get command return values). With no <scale>, numbers return as-is and lists/compounds return their length/size.

⚠ Version differences
  • Since 1.14: command storage (storage) introduced β€” the namespaced NBT database.
  • Since 1.15: data modify gained its current forms.
  • Since 1.19.4: string source (substring copy) added.
  • Since 1.20.5: most item NBT fields (tag.*) were replaced by data components (components.minecraft:*). Reading/writing item data now uses component paths. Entity and block-entity NBT paths (Health, Pos, Items, …) were largely unaffected.
πŸ“Œ Note

For item-specific data paths under components."minecraft:…" (the 1.20.5 migration) and the full old-tag-to-component mapping, see Data components. This page uses component paths only where an example touches an item field.

Examples

data/ns/function/db/demo.mcfunction β€” storage as a scratch database

# write
data modify storage ns:db player.lastLogin set value 1720000000L
data modify storage ns:db player.name set value "Steve"
# read (prints to chat / can be captured)
data get storage ns:db player
# remove
data remove storage ns:db player.lastLogin

data/ns/function/db/log_item.mcfunction β€” copy an entity's held item into storage, then append to a log list

data modify storage ns:db lastItem set from entity @s SelectedItem
data modify storage ns:log entries append from storage ns:db lastItem

data/ns/function/db/prefix.mcfunction β€” substring (1.19.4+): take first 3 chars of a stored string

data modify storage ns:db prefix set string storage ns:db player.name 0 3

data/ns/function/sign/edit.mcfunction β€” edit block-entity NBT (put custom text on a sign)

data merge block ~ ~1 ~ {front_text:{messages:['"Line 1"','""','""','""']}}
πŸ’‘ Best practice
  • Prefer storage over dummy entities (armor stands) for persistent data: no ticking cost, no despawn risk, and it survives across dimensions and chunk unloads.
  • Namespace storages per system (ns:config, ns:db, ns:tmp) and treat ns:tmp as clearable scratch.
  • data get on a compound/list returns its size β€” handy as a fast length check via execute store.

NBT format, SNBT & NBT paths

NBT (Named Binary Tag) is Minecraft's typed tree data format. In commands you write it as SNBT (Stringified NBT) β€” a JSON-like syntax where numeric types carry a type suffix.

Tag types & SNBT suffixes

TypeSuffixExampleNotes
Byteb / B1b, -5b−128…127; booleans true/false = 1b/0b
Shorts / S2000s−32768…32767
Int(none)3141592632-bit; default for bare whole numbers
Longl / L1720000000L64-bit
Floatf / F3.14f32-bit IEEE-754
Doubled / D or bare decimal3.14d, 3.1464-bit; bare decimals default to double
Stringquoted"hi", 'a "b"'double or single quotes
List[ ... ][1,2,3]ordered, all elements same type
Compound{ ... }{a:1b,b:"x"}unordered key→value map
Byte array[B; ... ][B;0b,1b,127b]typed array of bytes
Int array[I; ... ][I;1,2,3]typed array of ints (UUIDs use this)
Long array[L; ... ][L;1L,2L]typed array of longs

Quoting rules: a key or string may be unquoted if it contains only 0-9 A-Z a-z _ - . +. Otherwise quote it. If the string contains ", use '...' (and vice-versa). Escape with \", \', \\, \n, \t, etc.

Number niceties (modern parsers): hex 0xFF, binary 0b1010, scientific 1.2e3, and _ digit separators (1_000_000) are accepted. Bare decimals are doubles; you must add f for float.

NBT paths

A path navigates into a tag. Building blocks:

FormMeaningExample
nameChild tag by keyHealth
"quoted name"Key needing quotes (dots/colons/spaces)components."minecraft:custom_data"
a.b.cNested compoundsVillagerData.profession
list[i]Element at index (negative from end)Pos[0], Inventory[-1]
list[]Every element of the listItems[]
list[{filter}]Elements matching a compound filterInventory[{Slot:0b}]
name{filter}Named compound that matches filterVillagerData{profession:"minecraft:farmer"}
{filter}Root must match this compound{OnGround:1b}

A concrete deep example:

Items[{Slot:0b}].components."minecraft:written_book_content".pages[3].raw

Filters ([{...}], {...}) are also how execute if data tests existence:

execute if data entity @s Inventory[{id:"minecraft:diamond"}] run say has a diamond
⚠ Version differences
  • Since 1.20.5: item data moved from tag NBT to data components β€” paths like components."minecraft:custom_data".myFlag replace old tag.myFlag. See Data components.
  • Newer SNBT number features (hex/binary/underscores/true/false booleans) are recent-parser conveniences; on 1.21+ they are safe. Stick to explicit type suffixes for maximum cross-version portability.

Examples

data/ns/function/nbt/write.mcfunction β€” write nested custom data on an entity

data modify entity @s data set value {faction:"red",level:3,perks:["speed","haste"]}

data/ns/function/nbt/read.mcfunction β€” read one list element with a filter

data get entity @s Inventory[{Slot:0b}].count

execute store β€” bridging NBT and scores

Scoreboards hold ints; NBT holds typed numbers, strings, lists, compounds. execute store is the bridge: it captures a command's result (its numeric return) or success (0/1) and writes it into a score, storage/block/entity NBT, or bossbar.

Full syntax

execute store (result|success) score   <targets> <objective>                                                  ... run <command>
execute store (result|success) storage <id>     <path> (byte|short|int|long|float|double) <scale>            ... run <command>
execute store (result|success) block   <pos>    <path> (byte|short|int|long|float|double) <scale>            ... run <command>
execute store (result|success) entity  <target> <path> (byte|short|int|long|float|double) <scale>            ... run <command>
execute store (result|success) bossbar <id> (value|max)                                                      ... run <command>

Examples

data/ns/function/store/read_health.mcfunction β€” read entity NBT β†’ score (with scale for decimals)

# Health is a float; multiply by 100 and floor to keep 2 decimals as an int score
execute store result score @s health_x100 run data get entity @s Health 100
# note: 'data get ... <scale>' already scales+floors, so store captures the scaled int directly

data/ns/function/store/has_diamond.mcfunction β€” boolean flag from a data test (success)

execute store success score @s hasDiamond run data get entity @s Inventory[{id:"minecraft:diamond"}]
# hasDiamond = 1 if the path exists, else 0

data/ns/function/store/write_health.mcfunction β€” score β†’ NBT, scaling an int back into a float

# hearts is an int score; write it as a float Health field
execute store result entity @s Health float 0.5 run scoreboard players get @s hearts

data/ns/function/store/count_mobs.mcfunction β€” count matching entities into a score

execute store result score #global mobCount run execute if entity @e[type=zombie]
# 'if entity' returns the count of matches as its result
πŸ’‘ Best practice
  • store result score floors; multiply by a power of 10 before storing to retain decimals as fixed-point.
  • When writing back to a float/double NBT field, pick <scale> = 1/(your fixed-point factor) so the int score becomes the correct fractional value.
  • store success + if/unless is the canonical way to turn a condition into a persistent 0/1 flag.

Data structures & macro integration

Command storage + data modify ... set value lets you build arbitrary JSON-like trees (maps, lists, nested compounds). Combined with macros (function ... with storage, 1.20.2+) you get data-driven command generation: values pulled from storage are substituted into command text at call time.

Storage as arrays & maps

# a map (compound)
data modify storage ns:db config set value {maxPlayers:8,pvp:1b,motd:"Welcome"}
# a list used as an array/stack/queue
data modify storage ns:db queue set value []
data modify storage ns:db queue append value {id:1,task:"spawn"}   # push back
data modify storage ns:db queue prepend value {id:0,task:"init"}   # push front
data remove storage ns:db queue[0]                                 # pop front
# nested growth
data modify storage ns:db players.Steve.coins set value 100

Macros 1.20.2+

Full example β€” a data-driven "spawn shop"

data/ns/function/shop/buy.mcfunction β€” the macro function

$scoreboard players remove @s coins $(price)
$give @s $(item) $(count)
$tellraw @s [{"text":"Bought "},{"text":"$(count)x $(item)","color":"green"},{"text":" for $(price) coins"}]

data/ns/function/shop/load.mcfunction β€” define the shop as data

data modify storage ns:shop items set value {\
  sword:{item:"minecraft:iron_sword",count:1,price:50},\
  bread:{item:"minecraft:bread",count:16,price:10}\
}

data/ns/function/shop/buy_sword.mcfunction β€” gate in the caller, then run the macro

# only proceed if the player can afford it, then run the macro with the item's compound
execute if score @s coins matches 50.. run function ns:shop/buy with storage ns:shop items.sword

Here items.sword = {item:...,count:1,price:50}, and each key becomes $(item), $(count), $(price) inside the macro. Adding a new shop item is pure data β€” no new commands.

🎩 Trick
  • Self-referential state machines: keep state in storage; a macro function branches on $(state) and writes the next state back β€” a compact FSM without a scoreboard per state.
  • Dynamic function dispatch: store a function id string and call it with a macro line whose whole command is built from data: $function $(next) β€” data chooses which function runs.
  • Templated tellraw/particles/summons: build the command string from stored parameters; one macro function serves unlimited configured variants.
🚧 Workaround
  • Validate/clamp values before feeding them to macros β€” a macro line is executed verbatim, so malformed data becomes a malformed command. Macros are a mild injection surface; never feed unsanitized player text.
  • Keep macro functions small; do conditional gating in the caller (execute if ... run function ... with ...).
  • Cache the argument compound in ns:tmp right before the call so the macro reads a stable snapshot.
⚠ Version differences
  • Since 1.14: command storage introduced.
  • Since 1.19.4: string substring source (useful for parsing macro-bound text).
  • Since 1.20.2: function macros ($, $(...), function ... with ...) added β€” the enabler for this pattern.

Data types, precision & fixed-point math

Scoreboards are 32-bit signed ints only. NBT has byte/short/int/long/float/double. There is no native decimal scoreboard math. All "decimals" on scoreboards are fixed-point: store value Γ— 10^k as an int and remember the implied decimal places.

Precision gotchas

Fixed-point recipe (2 decimals)

data/ns/function/math/fixed_point.mcfunction

# constants: SCALE = 100
scoreboard players set #100 const 100
# set 3.75  ->  375
scoreboard players set #x math 375
# multiply two fixed-point numbers a*b (both Γ—100) -> divide by 100 to re-normalize
scoreboard players operation #x math *= #y math      # now Γ—10000
scoreboard players operation #x math /= #100 const   # back to Γ—100
# display with 2 decimals via numberformat/macro, or split int/frac:
scoreboard players operation #whole math = #x math
scoreboard players operation #whole math /= #100 const   # integer part
scoreboard players operation #frac math = #x math
scoreboard players operation #frac math %= #100 const    # fractional part (0-99)
🎩 Trick
  • Fake-player constants: reserve holders like #100 const 100, #2 const 2 in a const objective and reuse them across all operation math. Prefix with # so they never render on a sidebar and can't collide with real usernames (# isn't a legal username char).
  • Fixed-point decimals: store currency/percentages as value Γ— 100 (or Γ— 1000) ints; only convert to float at the display boundary.
  • UUID storage: an entity UUID is an int array UUID:[I;a,b,c,d]. Copy it with data modify storage ns:db owner set from entity @s UUID, and target it later via the @e[...] + if data filter or by reconstructing the selector β€” storage keeps a stable handle to a specific entity across ticks without a scoreboard tag.
  • Self-referential storage state machines: keep {state:"idle",tick:0} in storage; each tick a macro reads $(state), does work, and writes the next state β€” a persistent FSM with zero entities.
  • Global/system scores: use a single fake player like #global in a sys objective for world-level counters (mob caps, timers) instead of attaching them to a real entity.
πŸ’‘ Best practice
  • Treat an int score as the single source of truth for any quantity that must be exact (money, counts, timers). Derive float NBT from it, never the reverse.
  • Pick the largest scale factor that keeps all intermediate products under 2^31; document the implied decimals.
  • Reset scratch holders (#tmp) at the start of each computation to avoid stale-value bugs.
  • Namespace and #-prefix all constant/temp fake players so they never leak into player-facing displays.

Version summary

FeatureVersionSnapshot
Scoreboard system1.513w04a
trigger criterion, entity score holders1.814w06a
xp/food/air criteria1.915w32b
Command storage (storage)1.14β€”
Function macros ($, ... with ...)1.20.2β€”
numberformat / displayautoupdate1.20.323w46a
data modify ... string substring source1.19.4β€”
Item NBT β†’ data components migration1.20.5β€” (see Data components)

Sources