# Slot (Item Stack) Wire Format and Component Evolution > Cross-references: [`../01-data-types.md`](../01-data-types.md) · [`../versions/1.13.md`](../versions/1.13.md) · [`../versions/1.20.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 - `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.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). The `itemDamage` field 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();` > 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.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 == -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 evidence** — `ItemType1_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.json` — `types.{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 : // 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.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_name` component - `tag.display.Lore` → `lore` component - `tag.Enchantments` → `enchantments` component - `tag.Unbreakable` → `unbreakable` component - `tag.CustomModelData` → `custom_model_data` component - `tag.AttributeModifiers` → `attribute_modifiers` component - `tag.CanPlaceOn` → `can_place_on` component - `tag.CanDestroy` → `can_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 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 - **`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.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 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.