Named Binary Tags (NBT)

Read and write the tag data Bedrock stores on items.

NBT (Named Binary Tag) is the key/value format Bedrock uses to store structured data - on items, blocks, and entities. Endstone exposes it through the tag types in endstone/nbt, so you read and edit it without touching the binary format. The examples alias the namespace with namespace es = endstone;.

Tag types

Every value is a typed tag. Scalars expose .value(); the container tags nest other tags:

TagHolds
ByteTag, ShortTag, IntTag, LongTagan integer
FloatTag, DoubleTaga float
StringTaga string
ByteArrayTag, IntArrayTagan array of integers
ListTaga sequence of tags (all the same type)
CompoundTaga map of named tags

CompoundTag indexes by key with operator[] and iterates as {key, tag} pairs; a Tag extracted from it is read back with get<T>() (throws on a type mismatch) or get_if<T>() (returns a pointer, or nullptr).

Read an item's NBT

ItemStack::getNbt() returns a CompoundTag. Iterate it, or pull a scalar out with get_if:

include/my_plugin.h
#include <endstone/endstone.hpp>

namespace es = endstone;

// `player` is an endstone::Player
auto item = player.getInventory().getItemInMainHand();
if (!item) {
    return;
}

es::CompoundTag tag = item->getNbt();
for (const auto &[key, value] : tag) {
    getLogger().info("{}", key);
}

if (auto *level = tag["my_plugin:level"].get_if<es::IntTag>()) {
    int value = level->value();
}

Write NBT

Edit the CompoundTag and hand it back with setNbt. Wrap raw values in the matching tag type:

es::CompoundTag tag = item->getNbt();
tag["my_plugin:level"] = es::IntTag(5);
tag["my_plugin:owner"] = es::StringTag("Steve");
item->setNbt(tag);

Namespace your keys (my_plugin:level, not level) so they can't collide with the vanilla data Bedrock keeps on the same item.

Build nested structures

CompoundTag and ListTag nest, so you can build whatever shape you need. CompoundTag also takes an initializer list of key/tag pairs:

es::CompoundTag stats{{"kills", es::IntTag(10)}};

es::ListTag lore;
lore.emplace_back(es::StringTag("Forged in fire"));
stats["lore"] = lore;

Serialize and load

To persist NBT or move it between systems, nbt::dump() writes the binary form and nbt::load() reads it back. Bedrock uses little-endian byte order, which is the default:

std::string data = es::nbt::dump(tag);     // little-endian
es::Tag restored = es::nbt::load(data);

Pass std::endian::big for Java-style NBT, or network=true for Bedrock's network varint encoding.

On this page