---
title: Items and slots
description: 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](/packets/play/containersetslot), [ContainerSetContent](/packets/play/containersetcontent), [SetPlayerInventory](/packets/play/setplayerinventory), [SetCursorItem](/packets/play/setcursoritem), [SetEquipment](/packets/play/setequipment), [MerchantOffers](/packets/play/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.

```ts
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](#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.

```ts
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:

```ts
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`](/docs/chunks) takes. They are pinned to 26.2, not interchangeable across Minecraft versions. The [regeneration script](https://github.com/GDLev/mc-protocol/blob/master/tools/generate-items.ts) 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](/types#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`.

```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

```ts
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](/packets/play/containerclick) does **not** carry this type. It uses `HashedSlot` (`itemId`, `itemCount`, then `{ type, hash }` pairs with no component payloads), which `createSlot` cannot build. [SetCreativeModeSlot](/packets/play/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.

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

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