Control-state catalogs (handshake/status/login/config), categorized Play catalog (~182 packets), and deep-dives on the four hard formats: chunk data (paletted containers + light), entity metadata (type registry), slot/structured components, command graph (Brigadier). Sourced from minecraft-data + ViaVersion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
14 KiB
Slot (Item Stack) Wire Format and Component Evolution
Cross-references:
../01-data-types.md·../versions/1.13.md·../versions/1.20.md
A Slot is the on-wire representation of an item stack. It appears in dozens of play-phase packets (Set Slot, Window Items, Creative Inventory Action, Entity Equipment, …). Its layout has changed three times at major protocol boundaries.
Era 1 — Pre-1.13 (≤ protocol 340)
Source: minecraft-data/data/pc/1.12.2/protocol.json — types.slot; ViaVersion ItemType1_8.java
Slot {
blockId : i16 // item numeric ID; -1 = empty slot
if blockId != -1 {
itemCount : i8 // stack size (1–64)
itemDamage : i16 // damage / metadata value
nbtData : NBT // TAG_Compound or 0x00 byte (no tag)
}
}
Notes
blockIdwas the numeric item registry ID assigned by Mojang and exposed viaids.json. Negative values (blockId < 0, conventionally-1) signal an empty slot.itemDamage(also called metadata or aux) served double duty:- For tools/weapons: the amount of durability consumed.
- For items like wool, dye, or spawn eggs: a sub-type discriminator (e.g. wool damage=14 → red wool).
- Together,
(blockId, itemDamage)formed the full item identity.
nbtDatais a named compound tag serialised directly into the packet without a length prefix. The "named" form means the stream contains:0x0A(TAG_Compound type byte) · UTF-16BE length-prefixed name (empty string0x00 0x00for anonymous root) · tag payload. A leading0x00(TAG_End type byte) signals no tag. This is uncompressed network NBT — not gzip-wrapped. TheoptionalNbtminecraft-data abstract type maps to this idiom; its value is"native"meaning the codec is supplied by the runtime (NamedCompoundTagType, ViaVersionapi/.../misc/NamedCompoundTagType.java:62).
Era 2 — 1.13 / The Flattening (protocol 393)
Source: minecraft-data/data/pc/1.13/protocol.json — types.slot; ViaVersion ItemType1_13.java
Slot {
itemId : i16 // flat numeric ID; -1 = empty
if itemId != -1 {
itemCount : i8
nbtData : NBT // same named-compound encoding as pre-1.13
}
}
What changed
- The Flattening (MC-Java 1.13) merged the
(id, damage)identity pair into a single flat item ID. Each old(blockId, damage)pair that was a distinct item became its own registry entry (e.g. the 16 wool colours became 16 separate item IDs). TheitemDamagefield was removed from the Slot wire format. - The sentinel is still
i16 == -1(not a boolean). - NBT encoding unchanged.
ViaVersion evidence —
ItemType1_13.java:39:short id = buffer.readShort();thenitem.setAmount(buffer.readByte())andTypes.NAMED_COMPOUND_TAG.read(buffer). No damage field read.
Era 3 — 1.13.2 → 1.20.4 (protocols 404 – 765)
Source: minecraft-data/data/pc/1.13.2/protocol.json — types.slot; ViaVersion ItemType1_13_2.java, ItemType1_20_2.java
Slot {
present : bool // false = empty slot
if present {
itemId : VarInt // flat numeric ID
itemCount : i8 // stack size
nbtData : NBT // named compound tag or 0x00
}
}
What changed
- The empty-slot sentinel flipped from
i16 == -1to a dedicated booleanpresentbyte (0x00/0x01). itemIdwidened fromi16to VarInt in the same change (1.13.2-pre1).itemCountremainedi8throughout this era.- 1.20.2 (protocol 764) switched the
nbtDatafrom named to anonymous NBT (no name prefix in the stream;Types.COMPOUND_TAGvsTypes.NAMED_COMPOUND_TAGin ViaVersionItemType1_20_2.java:47) — but the field is otherwise identical.
ViaVersion evidence —
ItemType1_13_2.java:39:boolean present = buffer.readBoolean();thenTypes.VAR_INT.readPrimitive(buffer)+buffer.readByte()+Types.NAMED_COMPOUND_TAG.read(buffer).
This era spans 1.13.2 through 1.20.3 with only NBT-encoding tweaks. The logical layout (present + id + count + tag) was stable for ~5 years.
Era 4 — 1.20.5+ Structured Components (protocol 766+)
Sources:
minecraft-data/data/pc/1.20.5/protocol.json—types.{Slot,SlotComponent,SlotComponentType}minecraft-data/data/pc/1.21.1/protocol.json— same keysViaVersionItemType1_20_5.java,StructuredDataType.java,StructuredDataKey.java
Wire layout
Slot {
itemCount : VarInt // 0 = empty slot
if itemCount > 0 {
itemId : VarInt
numComponentsToAdd : VarInt
numComponentsToRemove : VarInt
// Add array — length = numComponentsToAdd
components[] {
componentType : VarInt // SlotComponentType id
data : <typed> // depends on componentType
}
// Remove array — length = numComponentsToRemove
removeComponents[] {
componentType : VarInt // marks this component absent
}
}
}
What changed
- The
presentboolean +nbtDatatag are gone. Empty is now signalled byitemCount == 0. - The single freeform NBT
tagkey was replaced by typed structured components: a registry of named data shapes. Each component is identified by a VarInt ID and has its own typed encoding. numComponentsToAdd/numComponentsToRemoveare both typically zero for items that carry only default properties; a non-zero count means the item overrides or explicitly clears default component values.- The "remove" array carries only the component type IDs (no data), marking those components explicitly absent (overrides the item type's defaults).
itemCount type discrepancy
minecraft-data 1.20.5 records itemCount as i8. ViaVersion's ItemType1_20_5.java:51
reads it as VAR_INT. ViaVersion is tested against live servers; the minecraft-data
entry is believed to be an artifact of the dataset's snapshot timing.
From 1.21.2 onwards minecraft-data also records varint, confirming the ViaVersion
reading.
SlotComponentType Registry
The component type ID is a VarInt whose mapping to named components is version-specific. The tables below show the 1.20.5 and 1.21.1 registries.
1.20.5 (minecraft-data/data/pc/1.20.5/protocol.json → types.SlotComponentType.mappings)
1.21.1 (minecraft-data/data/pc/1.21.1/protocol.json → types.SlotComponentType.mappings)
| ID (1.20.5) | ID (1.21.1) | Component name | Wire encoding (Add payload) |
|---|---|---|---|
| 0 | 0 | custom_data |
anonymousNbt (TAG_Compound) |
| 1 | 1 | max_stack_size |
VarInt |
| 2 | 2 | max_damage |
VarInt |
| 3 | 3 | damage |
VarInt |
| 4 | 4 | unbreakable |
bool (show tooltip flag) |
| 5 | 5 | custom_name |
anonymousNbt (text component) |
| 6 | 6 | item_name |
anonymousNbt (text component) |
| 7 | 7 | lore |
VarInt count + anonymousNbt[] |
| 8 | 8 | rarity |
VarInt (0=common 1=uncommon 2=rare 3=epic) |
| 9 | 9 | enchantments |
VarInt count + {id:VarInt, level:VarInt}[] + bool showTooltip |
| 10 | 10 | can_place_on |
VarInt count + ItemBlockPredicate[] + bool showTooltip |
| 11 | 11 | can_break |
VarInt count + ItemBlockPredicate[] + bool showTooltip |
| 12 | 12 | attribute_modifiers |
VarInt count + {typeId, uuid¹, name, value:f64, operation, slot}[] + bool showTooltip |
| 13 | 13 | custom_model_data |
VarInt |
| 14 | 14 | hide_additional_tooltip |
void (presence-only) |
| 15 | 15 | hide_tooltip |
void |
| 16 | 16 | repair_cost |
VarInt |
| 17 | 17 | creative_slot_lock |
void |
| 18 | 18 | enchantment_glint_override |
bool |
| 19 | 19 | intangible_projectile |
anonymousNbt |
| 20 | 20 | food |
{nutrition:VarInt, saturationModifier:f32, canAlwaysEat:bool, secondsToEat:f32, usingConvertsTo:Slot, effects:[]} |
| 21 | 21 | fire_resistant |
void |
| 22 | 22 | tool |
{rules:[], defaultMiningSpeed:f32, damagePerBlock:VarInt} |
| 23 | 23 | stored_enchantments |
same as enchantments |
| 24 | 24 | dyed_color |
{color:i32, showTooltip:bool} |
| 25 | 25 | map_color |
i32 (RGB) |
| 26 | 26 | map_id |
VarInt |
| 27 | 27 | map_decorations |
anonymousNbt |
| 28 | 28 | map_post_processing |
VarInt |
| 29 | 29 | charged_projectiles |
VarInt count + Slot[] |
| 30 | 30 | bundle_contents |
VarInt count + Slot[] |
| 31 | 31 | potion_contents |
{potionId:VarInt?, customColor:i32?, customEffects:[], customName:string?} |
| 32 | 32 | suspicious_stew_effects |
VarInt count + {effect:VarInt, duration:VarInt}[] |
| 33 | 33 | writable_book_content |
VarInt count + ItemBookPage[] |
| 34 | 34 | written_book_content |
{rawTitle, filteredTitle?, author, generation:VarInt, pages[], resolved:bool} |
| 35 | 35 | trim |
{material:RegistryEntry, pattern:RegistryEntry, showInTooltip:bool} |
| 36 | 36 | debug_stick_state |
anonymousNbt |
| 37 | 37 | entity_data |
anonymousNbt |
| 38 | 38 | bucket_entity_data |
anonymousNbt |
| 39 | 39 | block_entity_data |
anonymousNbt |
| 40 | 40 | instrument |
RegistryEntryHolder (inline or id ref) |
| 41 | 41 | ominous_bottle_amplifier |
VarInt |
| 42 | — | recipes (1.20.5 only) |
anonymousNbt |
| — | 42 | jukebox_playable (1.21+ only) |
{hasHolder:bool, song, showInTooltip:bool} |
| 43 | 42 | jukebox_playable / recipes |
(see above — IDs shift by 1 after id 41 in 1.21) |
| 44 | 43 | lodestone_tracker |
{globalPosition:optional {dimension:string, position:Position}, tracked:bool} |
| 45 | 44 | firework_explosion |
ItemFireworkExplosion |
| 46 | 45 | fireworks |
{flightDuration:VarInt, explosions:[]} |
| 47 | 46 | profile |
{name:string?, uuid:UUID?, properties:[{name, value, signature?}]} |
| 48 | 47 | note_block_sound |
string (resource location) |
| 49 | 48 | banner_patterns |
VarInt count + BannerPatternLayer[] |
| 50 | 49 | base_color |
VarInt (DyeColor) |
| 51 | 50 | pot_decorations |
VarInt count + VarInt[] (item ids) |
| 52 | 51 | container |
VarInt count + Slot[] |
| 53 | 52 | block_state |
VarInt count + {property:string, value:string}[] |
| 54 | 53 | bees |
VarInt count + {nbtData:anonymousNbt, ticksInHive:VarInt, minTicksInHive:VarInt}[] |
| 55 | 54 | lock |
anonymousNbt |
| 56 | 55 | container_loot |
anonymousNbt (CompoundTag with loot table + seed) |
¹ attribute_modifiers in 1.20.5 includes a UUID field that was removed in 1.21.
ID shift: 1.20.5 → 1.21
In 1.20.5 recipes occupies id 42 and jukebox_playable is absent (it was
added in MC 1.21). In 1.21+, jukebox_playable is inserted at id 42 and
recipes shifts to 43. All subsequent IDs shift by +1. Parsers must never
hardcode component IDs across versions — always consult the per-version registry.
NBT → Components: custom_data and backward compatibility
Before 1.20.5, all item metadata lived in the free-form NBT tag compound.
ViaVersion's StructuredDataConverter (path:
common/.../protocols/v1_20_3to1_20_5/rewriter/StructuredDataConverter.java)
translates the old NBT keys into the appropriate components during downgrade:
tag.display.Name→custom_namecomponenttag.display.Lore→lorecomponenttag.Enchantments→enchantmentscomponenttag.Unbreakable→unbreakablecomponenttag.CustomModelData→custom_model_datacomponenttag.AttributeModifiers→attribute_modifierscomponenttag.CanPlaceOn→can_place_oncomponenttag.CanDestroy→can_breakcomponent- Data not expressible in any known component is preserved in
custom_dataunder aVV|DataComponentsbackup key.
The StructuredDataConverter constants for the old hide-flags bitfield (HIDE_ENCHANTMENTS=1,
HIDE_ATTRIBUTES=2, HIDE_UNBREAKABLE=4, …) appear at lines 76–83 of that file,
confirming how the HideFlags NBT int mapped to the old tooltip-suppression system
that components replace with per-component showTooltip booleans.
Summary of format evolution
| Era | Versions | Empty sentinel | Item ID | Count | Sub-type | Metadata |
|---|---|---|---|---|---|---|
| Pre-1.13 | ≤ 1.12.x | i16 < 0 |
i16 |
i8 |
i16 damage |
freeform NBT |
| 1.13 | 1.13 – 1.13.1 | i16 < 0 |
i16 (flat) |
i8 |
— | freeform NBT |
| 1.13.2 – 1.20.4 | 1.13.2 – 1.20.3 | bool false |
VarInt |
i8 |
— | freeform NBT |
| 1.20.5+ | 1.20.5+ | VarInt == 0 |
VarInt |
VarInt |
— | typed components |
Open items
itemCounti8 vs VarInt in 1.20.5: minecraft-data recordsi8; ViaVersionItemType1_20_5.javareadsVAR_INT. The ViaVersion implementation is tested against live traffic and is treated as authoritative here, but the exact protocol snapshot where Mojang switched is unconfirmed.- 1.20.2 NBT format: ViaVersion
ItemType1_20_2.javausesTypes.COMPOUND_TAG(anonymous, no name prefix) while earlier versions usedTypes.NAMED_COMPOUND_TAG. The exact protocol version of that sub-change within the 1.13.2–1.20.4 era is confirmed as 1.20.2 by the class name but not independently verified against protocol.json (1.20.2 and 1.20.3 files share the samepresent+id+count+nbtshape in minecraft-data). jukebox_playablewire shape in 1.20.5: present in the component type enum at id 42 per minecraft-data 1.20.5? The diff above showsrecipesat 42 in 1.20.5 andjukebox_playableabsent — consistent with Mojang addingjukebox_playablein the 1.21 pre-releases. If a 1.20.5 release-candidate snapshot introduced it mid-stream, that is not captured in the current minecraft-data snapshot.