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

Wire types

The primitive types every packet is built from, and what JavaScript values they map to.

Every packet field in the Packets reference is one of the types below, an array of them, or a struct of them. This page tells you what to pass to sendPacket and what you get back from a parsed packet.

All types are big-endian. Each lives in src/Core/Types/<Name>.ts and exports read(buf, off) and write(value).

Numbers

Type Bytes JS value Notes
Boolean 1 boolean 0x00 or 0x01.
Byte 1 number Signed, -128 to 127.
UByte 1 number Unsigned, 0 to 255.
Short 2 number Signed. Throws RangeError on a truncated buffer.
UShort 2 number Unsigned, 0 to 65535. Used for the port in Intension.
Int 4 number Signed 32-bit.
Long 8 bigint Read as bigint. Write accepts bigint, number, or a numeric string.
Float 4 number IEEE 754 single.
Double 8 number IEEE 754 double.
VarInt 1–5 number See below.
VarLong 1–10 bigint Same encoding as VarInt over 64 bits. Write accepts bigint, number, or string.
Angle 1 number Rotation in 1/256ths of a turn, 0 to 255. Used for entity yaw/pitch.

VarInt

Seven payload bits per byte, low bits first, high bit set on every byte except the last. Values fit in 1 to 5 bytes; a sixth byte throws RangeError: VarInt exceeds 5 bytes. Negative numbers always take 5 bytes.

VarInt is also what prefixes every frame, every string, and every array in the protocol, so it is the most common type on the wire by far.

VarInt.write(300);            // <Buffer ac 02>
VarInt.read(buf, { value: 0 }); // 300, and off.value is now 2

Wire types live in src/Core/Types/ in this repository. The published package only exports ., ./26.2, ./776, and ./package.json, so import … from "mc-protocol/src/Core/Types/…" will not resolve for consumers.

read functions take a mutable offset object and advance off.value past the bytes they consumed.

Text and identifiers

Type Encoding JS value
String VarInt byte length, then UTF-8 string
UUID 16 raw bytes 32-character lowercase hex string, no dashes. Write parses hex two characters at a time, so pass it undashed.
TextComponent Network NBT Plain object, see below

Identifiers like minecraft:overworld are ordinary Strings.

TextComponent and NBT

Chat messages, titles, and item names are NBT text components. You pass a plain JavaScript value and writeNbt maps it:

JS value NBT tag
null / undefined End (0)
boolean Byte
integer number in int32 range Int
other number Double
bigint Long
string String
array List
object Compound
{ type, value } Used verbatim, for when you need a specific tag such as Byte Array

So { text: "Hello", color: "aqua" } becomes a compound with two string tags, which is what the client expects. Reading does the inverse and returns plain values.

Spatial

Position

A block position packed into one 64-bit integer: 26 bits X, 26 bits Z, 12 bits Y, all signed. JS value is { x, y, z }. Range is ±33 million horizontally and -2048 to 2047 vertically.

await client.sendPacket("DefaultSpawnPosition", {
  dimension: "minecraft:overworld",
  position: { x: 0, y: 64, z: 0 },
  yaw: 0, pitch: 0,
});

LpVec3

The 26.2 compressed velocity vector. JS value is [x, y, z]. A zero vector is one byte; anything else is 6 bytes with 15-bit quantized components plus a scale, and a VarInt continuation for magnitudes above 3. Precision is relative to the largest component, so tiny values next to large ones lose detail.

FixedBitSet

A fixed number of bytes with no length prefix. The packet definition supplies the length. JS value is a Buffer.

Composite shapes

The generated packet code combines primitives in a few standard ways. When a field table in the packet reference says one of these, this is what it means:

Shape Wire format JS value
Struct Fields in order, no header Plain object with those fields
Array VarInt count, then each element JS array
Optional Boolean present flag, then the value if true Value or null / undefined
Enum VarInt index Write: the string name or the number index. Read: { value, translation }, where value is the index and translation the name.
Byte array VarInt length, then raw bytes Buffer
ID or inline VarInt; 0 means an inline value follows, otherwise id - 1 number or object
ID set VarInt type; 0 means a tag name follows, otherwise type - 1 IDs { type, tagName } or { type, ids }

Larger game structures (Slot, item data components, particles, sounds, command nodes) are structs of the above and live in src/Core/Helpers/. Their exact layouts follow the vanilla 26.2 protocol.

Slot

One item stack. On the wire: VarInt itemCount, and when it is non-zero, VarInt itemId, VarInt counts of added and removed components, then the component arrays.

Writing, pass { itemCount: 0 } for an empty slot, or { itemCount, itemId, components, removeComponents }. Each entry in components is { type, data } and each entry in removeComponents is { type }, with type the unnamespaced 26.2 name such as custom_name or enchantments. The writer derives both counts from the array lengths; componentsToAdd and componentsToRemove are accepted as aliases.

Reading returns the same fields plus addedComponentCount and removedComponentCount, and every enum (including each component’s type) comes back as { value, translation }, for example { value: 6, translation: "custom_name" }. Decoded slots can be written back unchanged.

Two serverbound packets use related but distinct types. ContainerClick carries HashedSlot: itemId, itemCount, then { type, hash } pairs and a removeComponents list, with no component payloads. SetCreativeModeSlot carries UntrustedSlot: the same layout as Slot, but each component’s data is a length-prefixed raw Buffer instead of a decoded value.

You rarely need to build a Slot by hand. helpers.createSlot("diamond_sword", options) and helpers.emptySlot() return packet-ready objects. Short names and minecraft: names resolve through the bundled 26.2 item registry. helpers.getItemId(name) also provides the numeric ID for other packet fields; see Items and slots.

Field aliases

Most generated packets accept the vanilla field name as an alias when writing. Intension takes protocolVersion or protocol, serverHost or address, nextState or intent. The packet reference lists the aliases for each field.

Parsed packets usually use the write-side primary name. Two handshake/login packets do not: Intension reads protocol / address / port / intent and writes protocolVersion / serverHost / serverPort / nextState; Hello reads name / uuid and writes username / playerUUID. Lifecycle code accepts both sets.

Was this page helpful?