Skip to content
mc-protocol
Esc
↑↓navigate↵open⌘Jpreview
On this page

Items and slots

Give items by name, customize their components, and clear inventory slots without building buffers.

A Slot is the wire shape for one item stack: count, item ID, and the data components to add or remove. Six play packets carry it (ContainerSetSlot, ContainerSetContent, SetPlayerInventory, SetCursorItem, SetEquipment, MerchantOffers). Writing one by hand means knowing the 26.2 component names and their wire shapes. helpers.createSlot handles the shortcuts and validates the rest.

import { helpers } from "mc-protocol";

// Run on a server-side client connection already in play state.
await client.sendPacket("SetPlayerInventory", {
  slotId: 0, // first hotbar slot
  contents: helpers.createSlot("diamond_sword", {
    name: { text: "Excalibur", color: "gold" },
    lore: ["Forged in TypeScript"],
    unbreakable: true,
    glint: true,
  }),
});

The result is a plain object, not a Buffer. Pass it straight to sendPacket; the Slot writer derives the added and removed component counts from the arrays.

createSlot(item, options?)

Pass an item name such as "diamond_sword" or "minecraft:diamond_sword". Numeric item IDs still work. The helper returns { itemCount, itemId, components, removeComponents }, with the name resolved to its 26.2 ID. Everything in options is optional.

Option Type Default Component it sets
count integer, 1 to 2147483647 1 none (stack size)
name SlotText unset custom_name
lore SlotText[] unset lore (the array is copied)
damage integer, 0 to 2147483647 unset damage
unbreakable boolean unset unbreakable: true adds it, false removes it, omitted leaves the item default
glint boolean unset enchantment_glint_override
components Record<string, unknown> {} any component by codec name, see Advanced components
removeComponents string[] [] components to strip from the item default

Validation

createSlot throws before anything reaches the wire when:

  • An item name is unknown, has the wrong case, or uses a namespace other than minecraft (RangeError).
  • A numeric item ID, count, or damage is not an integer in 0 to 2147483647 (RangeError).
  • count is 0 (RangeError, pointing you at emptySlot()).
  • A component name is not a string (TypeError).
  • A component name is not in the 26.2 SlotComponentType enum (RangeError: Unknown enum value: …).
  • The same component is both added and removed (RangeError: Cannot both add and remove component: …).

Names are checked against the bundled registry. Numeric IDs are checked for range, not registry membership. Stack-size and durability limits remain your responsibility; advanced component payloads are passed to the existing wire codec.

Shortcut precedence

Shortcut options always win over the same key in components:

  • name replaces components.custom_name; lore replaces components.lore; damage replaces components.damage; glint replaces components.enchantment_glint_override.
  • unbreakable: true sets components.unbreakable (its wire payload is empty, so the value is undefined).
  • unbreakable: false deletes unbreakable from components and adds it to removeComponents. Because the shortcut removed the add first, this never trips the add-and-remove check on its own.
  • unbreakable omitted touches nothing; whatever you put in components or removeComponents stands.

emptySlot()

Returns { itemCount: 0 }. Use it to clear a slot; no item ID or components are written.

await client.sendPacket("SetPlayerInventory", { slotId: 0, contents: helpers.emptySlot() });

getItemId(name)

For packet fields that need a numeric item ID rather than a Slot, use the same lookup directly:

helpers.getItemId("diamond_sword");           // 964
helpers.getItemId("minecraft:diamond_sword"); // 964
helpers.getItemId("sulfur_cube_bucket");      // 1052

The library bundles all 1,537 vanilla item IDs from the official 26.2 / protocol 776 server’s reports/registries.json. No download or setup is needed at runtime. Names are case-sensitive; typos and modded namespaces throw instead of silently becoming air. "air" resolves to 0, but use emptySlot() when clearing an inventory slot.

These are item IDs, not the block state IDs that ChunkHelper.setBlock takes. They are pinned to 26.2, not interchangeable across Minecraft versions. The regeneration script records the official server download and checksum.

Text values

name and each lore line accept a SlotText: a plain string, or an NBT-compatible text component object such as { text: "Excalibur", color: "gold", italic: false }. Both are written with the NBT mapping described under TextComponent and NBT; a bare string becomes an NBT string tag, an object a compound.

Advanced components

Anything without a shortcut goes in components, keyed by the unnamespaced codec name (rarity, not minecraft:rarity). The value is the wire shape the 26.2 component writer expects. The full list of names and shapes is the SlotComponentType enum and writeSlotComponent switch in src/Core/Helpers/ProtocolTypes.ts.

function enchantedBooks(enchantmentId: number) {
  return helpers.createSlot("enchanted_book", {
    count: 16,
    components: {
      rarity: "rare",                                          // enum: "common" | "uncommon" | "rare" | "epic", or its index
      stored_enchantments: { enchantments: [{ id: enchantmentId, level: 3 }] }, // enchantment registry ID
      max_stack_size: 16,                                      // VarInt
    },
    removeComponents: ["repair_cost"],
  });
}

Shapes worth knowing:

  • Empty components (unbreakable, creative_slot_lock, intangible_projectile) take no data; use undefined.
  • Enums (rarity, and enum fields inside structs) accept either the string name or the numeric index when writing.
  • Structs are plain objects with the fields the writer reads, for example tooltip_display: { hideTooltip: false, hiddenComponents: [] } or custom_model_data: { floats: [], flags: [], strings: [], colors: [] }.
  • Nested slots use wrapper objects: bundle_contents and container take { contents: [helpers.createSlot("stone")] }; charged_projectiles takes { projectiles: [helpers.createSlot("arrow")] }. use_remainder takes a slot directly.
  • NBT components (custom_data, item_name) take any value writeNbt handles.

getItemId only resolves items. Other registry-backed IDs, such as enchantments and potions, still require their own registry data.

What you get back

helpers.createSlot("stone", { count: 2, name: "Hi", unbreakable: true });
// {
//   itemCount: 2,
//   itemId: 1,
//   components: [
//     { type: "custom_name", data: "Hi" },
//     { type: "unbreakable", data: undefined },
//   ],
//   removeComponents: [],
// }

Slots decoded from incoming packets differ in two ways. The reader adds the wire counts (addedComponentCount, removedComponentCount), and every enum is returned as { value, translation }, so a decoded component’s type is { value: 6, translation: "custom_name" } rather than the bare string. The writer accepts either form, and also the componentsToAdd and componentsToRemove aliases, so a decoded slot can be sent back out unchanged.

Serverbound ContainerClick does not carry this type. It uses HashedSlot (itemId, itemCount, then { type, hash } pairs with no component payloads), which createSlot cannot build. SetCreativeModeSlot uses UntrustedSlot, which keeps the Slot layout but carries each component’s data as a length-prefixed raw Buffer, so createSlot output is not directly valid there either.

Types

All exported through the helpers namespace:

Type Shape
Slot { itemCount: 0 } or { itemCount: number; itemId: number; components: SlotComponent[]; removeComponents: { type: string }[] }
SlotOptions the options table above
SlotComponent { type: string; data?: unknown }
SlotText string | Record<string, unknown>

Slot describes writer input: type is the string name. It is not the decoded shape, where type is the { value, translation } wrapper described above.

import type { helpers } from "mc-protocol";

function hotbar(items: helpers.Slot[]) { /* … */ }

Was this page helpful?