Files
minecraft_protocol/packets/format-slot-and-components.md
T
claude-timemachine 90b711d12a packets/: packet model + catalogs + wire-format deep-dives
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>
2026-06-19 15:15:26 +02:00

14 KiB
Raw Blame History

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.jsontypes.slot; ViaVersion ItemType1_8.java

Slot {
    blockId    : i16          // item numeric ID; -1 = empty slot
    if blockId != -1 {
        itemCount  : i8       // stack size (164)
        itemDamage : i16      // damage / metadata value
        nbtData    : NBT      // TAG_Compound or 0x00 byte (no tag)
    }
}

Notes

  • blockId was the numeric item registry ID assigned by Mojang and exposed via ids.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.
  • nbtData is 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 string 0x00 0x00 for anonymous root) · tag payload. A leading 0x00 (TAG_End type byte) signals no tag. This is uncompressed network NBT — not gzip-wrapped. The optionalNbt minecraft-data abstract type maps to this idiom; its value is "native" meaning the codec is supplied by the runtime (NamedCompoundTagType, ViaVersion api/.../misc/NamedCompoundTagType.java:62).

Era 2 — 1.13 / The Flattening (protocol 393)

Source: minecraft-data/data/pc/1.13/protocol.jsontypes.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). The itemDamage field was removed from the Slot wire format.
  • The sentinel is still i16 == -1 (not a boolean).
  • NBT encoding unchanged.

ViaVersion evidenceItemType1_13.java:39: short id = buffer.readShort(); then item.setAmount(buffer.readByte()) and Types.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.jsontypes.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 == -1 to a dedicated boolean present byte (0x00 / 0x01).
  • itemId widened from i16 to VarInt in the same change (1.13.2-pre1).
  • itemCount remained i8 throughout this era.
  • 1.20.2 (protocol 764) switched the nbtData from named to anonymous NBT (no name prefix in the stream; Types.COMPOUND_TAG vs Types.NAMED_COMPOUND_TAG in ViaVersion ItemType1_20_2.java:47) — but the field is otherwise identical.

ViaVersion evidenceItemType1_13_2.java:39: boolean present = buffer.readBoolean(); then Types.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.jsontypes.{Slot,SlotComponent,SlotComponentType}
  • minecraft-data/data/pc/1.21.1/protocol.json — same keys
  • ViaVersion ItemType1_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 present boolean + nbtData tag are gone. Empty is now signalled by itemCount == 0.
  • The single freeform NBT tag key 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 / numComponentsToRemove are 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.jsontypes.SlotComponentType.mappings)
1.21.1 (minecraft-data/data/pc/1.21.1/protocol.jsontypes.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.Namecustom_name component
  • tag.display.Lorelore component
  • tag.Enchantmentsenchantments component
  • tag.Unbreakableunbreakable component
  • tag.CustomModelDatacustom_model_data component
  • tag.AttributeModifiersattribute_modifiers component
  • tag.CanPlaceOncan_place_on component
  • tag.CanDestroycan_break component
  • Data not expressible in any known component is preserved in custom_data under a VV|DataComponents backup key.

The StructuredDataConverter constants for the old hide-flags bitfield (HIDE_ENCHANTMENTS=1, HIDE_ATTRIBUTES=2, HIDE_UNBREAKABLE=4, …) appear at lines 7683 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

  • itemCount i8 vs VarInt in 1.20.5: minecraft-data records i8; ViaVersion ItemType1_20_5.java reads VAR_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.java uses Types.COMPOUND_TAG (anonymous, no name prefix) while earlier versions used Types.NAMED_COMPOUND_TAG. The exact protocol version of that sub-change within the 1.13.21.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 same present+id+count+nbt shape in minecraft-data).
  • jukebox_playable wire shape in 1.20.5: present in the component type enum at id 42 per minecraft-data 1.20.5? The diff above shows recipes at 42 in 1.20.5 and jukebox_playable absent — consistent with Mojang adding jukebox_playable in 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.