Skip to main content
The NbtBinary class provides functionality for reading and writing NBT (Named Binary Tag) format, which is used throughout Minecraft for structured data storage.

Overview

NBT is a binary format used in:
  • level.dat - World settings and metadata
  • Block actors (tile entities) - Chests, signs, spawners, etc.
  • Entities - Mobs, items, armor stands
  • Structure files - .mcstructure files
  • Player data

Constructor

const nbt = new NbtBinary();
nbt.context = 'level.dat'; // Optional context for error messages

Loading NBT Data

fromBinary
(data: Uint8Array, littleEndian: boolean, isVarint: boolean, skipBytes?: number, stringsAreASCII?: boolean, processAsList?: boolean) => number
Parses NBT binary data.Parameters:
  • data - Binary data to parse
  • littleEndian - Use little-endian byte order (true for Bedrock)
  • isVarint - Use varint encoding for lengths
  • skipBytes - Number of bytes to skip at start (e.g., 8 for level.dat)
  • stringsAreASCII - Optimize for ASCII strings
  • processAsList - Parse as list of multiple root tags
Returns: Number of bytes read
const nbt = new NbtBinary();
const bytesRead = nbt.fromBinary(
  fileBytes,
  true,  // littleEndian for Bedrock
  false, // no varint
  8      // skip 8-byte header in level.dat
);

Writing NBT Data

toBinary
() => Uint8Array | undefined
Serializes the NBT structure to binary format.
const bytes = nbt.toBinary();
if (bytes) {
  await file.setContent(bytes);
}

Accessing Data

roots
NbtBinaryTag[] | null
Array of root tags. Most files have a single root.
if (nbt.roots && nbt.roots.length > 0) {
  const root = nbt.roots[0];
}
singleRoot
NbtBinaryTag | null
Convenience getter for the first root tag.
const root = nbt.singleRoot;
if (root) {
  const levelName = root.find('LevelName');
  console.log(levelName?.valueAsString);
}

Creating NBT Structure

ensureSingleRoot
() => NbtBinaryTag
Creates or returns the single root compound tag.
const root = nbt.ensureSingleRoot();
root.ensureTag('LevelName', NbtTagType.string).value = 'My World';
root.ensureTag('SpawnX', NbtTagType.int).value = 0;
root.ensureTag('SpawnY', NbtTagType.int).value = 100;
root.ensureTag('SpawnZ', NbtTagType.int).value = 0;

JSON Conversion

getJson
() => INbtTag
Converts NBT structure to JSON object.
const json = nbt.getJson();
console.log(JSON.stringify(json, null, 2));
getJsonString
() => string
Converts NBT structure to JSON string.
const jsonStr = nbt.getJsonString();

NbtBinaryTag

Individual NBT tags are represented by NbtBinaryTag:

Properties

type
NbtTagType
Tag type (compound, list, int, string, etc.).
name
string
Tag name (empty for list children).
value
string | number | bigint | bigint[] | number[] | boolean | null
Tag value (type depends on tag type).

Methods

find
(name: string) => NbtBinaryTag | null
Recursively searches for a tag by name.
const tag = root.find('LevelName');
if (tag) {
  console.log(tag.valueAsString);
}
child
(name: string) => NbtBinaryTag | null
Gets a direct child tag by name.
const abilities = root.child('abilities');
const mayFly = abilities?.child('mayfly');
ensureTag
(tagName: string, tagType: NbtTagType) => NbtBinaryTag
Gets or creates a tag.
const levelName = root.ensureTag('LevelName', NbtTagType.string);
levelName.value = 'New Name';
addTag
(tagType: NbtTagType, tagName?: string) => NbtBinaryTag
Adds a new tag.
const newTag = root.addTag(NbtTagType.int, 'MyValue');
newTag.value = 42;
removeTag
(tagName: string) => boolean
Removes a tag by name.
root.removeTag('oldProperty');
getTagChildren
() => NbtBinaryTag[]
Gets all child tags (excludes end tag).
for (const child of root.getTagChildren()) {
  console.log(`${child.name}: ${child.valueAsString}`);
}

Value Accessors

valueAsString
string
Gets value as string.
const name = tag.valueAsString;
valueAsInt
number
Gets value as integer.
const x = tag.valueAsInt;
valueAsFloat
number
Gets value as float.
valueAsBigInt
bigint
Gets value as bigint.
const time = tag.valueAsBigInt;
valueAsBoolean
boolean
Gets value as boolean.
const enabled = tag.valueAsBoolean;
valueAsNumericArray
number[]
Gets list children as numeric array.
const version = tag.valueAsNumericArray; // [1, 20, 0]
valueAsJSONObject
any
Parses string value as JSON.
const layers = tag.valueAsJSONObject;

NBT Tag Types

enum NbtTagType {
  end = 0,        // End of compound
  byte = 1,       // 8-bit signed integer
  short = 2,      // 16-bit signed integer
  int = 3,        // 32-bit signed integer
  long = 4,       // 64-bit signed integer
  float = 5,      // 32-bit float
  double = 6,     // 64-bit float
  byteArray = 7,  // Array of bytes
  string = 8,     // UTF-8 string
  list = 9,       // List of tags (same type)
  compound = 10,  // Named tags
  intArray = 11,  // Array of ints
  longArray = 12  // Array of longs
}

Example: Reading level.dat

import { NbtBinary, NbtTagType } from '@minecraft/creator-tools';

async function readLevelDat(file: IFile) {
  await file.loadContent();
  const bytes = file.content as Uint8Array;
  
  const nbt = new NbtBinary();
  nbt.context = 'level.dat';
  
  // level.dat has 8-byte header before NBT data
  nbt.fromBinary(bytes, true, false, 8);
  
  const root = nbt.singleRoot;
  if (!root) {
    console.error('No root tag found');
    return;
  }
  
  // Read basic properties
  const levelName = root.find('LevelName')?.valueAsString;
  const spawnX = root.find('SpawnX')?.valueAsInt;
  const spawnY = root.find('SpawnY')?.valueAsInt;
  const spawnZ = root.find('SpawnZ')?.valueAsInt;
  const gameType = root.find('GameType')?.valueAsInt;
  
  console.log(`World: ${levelName}`);
  console.log(`Spawn: (${spawnX}, ${spawnY}, ${spawnZ})`);
  console.log(`Game Type: ${gameType}`);
  
  // Read experiments
  const experiments = root.find('experiments');
  if (experiments) {
    const betaApis = experiments.child('gametest')?.valueAsBoolean;
    console.log(`Beta APIs: ${betaApis}`);
  }
}

Example: Modifying NBT

async function modifyLevelDat(file: IFile) {
  await file.loadContent();
  const bytes = file.content as Uint8Array;
  
  const nbt = new NbtBinary();
  nbt.fromBinary(bytes, true, false, 8);
  
  const root = nbt.ensureSingleRoot();
  
  // Modify properties
  root.ensureTag('LevelName', NbtTagType.string).value = 'Modified World';
  root.ensureTag('commandsEnabled', NbtTagType.byte).value = 1;
  root.ensureTag('showcoordinates', NbtTagType.byte).value = 1;
  
  // Enable experiment
  const experiments = root.ensureTag('experiments', NbtTagType.compound);
  experiments.ensureTag('gametest', NbtTagType.byte).value = 1;
  
  // Save with 8-byte header
  const nbtBytes = nbt.toBinary();
  if (nbtBytes) {
    const fullBytes = new Uint8Array(nbtBytes.length + 8);
    fullBytes.set(nbtBytes, 8);
    
    // Write header (version 10, length)
    fullBytes[0] = 10; fullBytes[1] = 0; fullBytes[2] = 0; fullBytes[3] = 0;
    fullBytes[4] = nbtBytes.length & 0xFF;
    fullBytes[5] = (nbtBytes.length >> 8) & 0xFF;
    fullBytes[6] = (nbtBytes.length >> 16) & 0xFF;
    fullBytes[7] = (nbtBytes.length >> 24) & 0xFF;
    
    file.setContent(fullBytes);
    await file.saveContent();
  }
}

Example: Reading Block Actor Data

function parseChestData(nbtBytes: Uint8Array) {
  const nbt = new NbtBinary();
  nbt.context = 'Chest';
  nbt.fromBinary(nbtBytes, true, false, 0, true);
  
  const root = nbt.singleRoot;
  if (!root) return;
  
  // Get chest position
  const x = root.find('x')?.valueAsInt;
  const y = root.find('y')?.valueAsInt;
  const z = root.find('z')?.valueAsInt;
  
  console.log(`Chest at (${x}, ${y}, ${z})`);
  
  // Get items
  const items = root.find('Items');
  if (items) {
    const itemList = items.getTagChildren();
    console.log(`Contains ${itemList.length} items:`);
    
    for (const item of itemList) {
      const name = item.find('Name')?.valueAsString;
      const count = item.find('Count')?.valueAsInt;
      const slot = item.find('Slot')?.valueAsInt;
      console.log(`  Slot ${slot}: ${name} x${count}`);
    }
  }
}

Example: Creating NBT Structure

function createStructure() {
  const nbt = new NbtBinary();
  const root = nbt.ensureSingleRoot();
  
  // Add basic types
  root.ensureTag('name', NbtTagType.string).value = 'My Structure';
  root.ensureTag('version', NbtTagType.int).value = 1;
  root.ensureTag('size', NbtTagType.list).setListFromArray([16, 16, 16]);
  
  // Add compound
  const metadata = root.ensureTag('metadata', NbtTagType.compound);
  metadata.ensureTag('author', NbtTagType.string).value = 'Player';
  metadata.ensureTag('created', NbtTagType.long).value = BigInt(Date.now());
  
  // Add list of compounds
  const blocks = root.ensureTag('blocks', NbtTagType.list);
  blocks.childTagType = NbtTagType.compound;
  
  for (let i = 0; i < 10; i++) {
    const block = blocks.addTag(NbtTagType.compound);
    block.ensureTag('pos', NbtTagType.list).setListFromArray([i, 0, 0]);
    block.ensureTag('state', NbtTagType.int).value = 0;
  }
  
  // Serialize
  const bytes = nbt.toBinary();
  return bytes;
}

Error Handling

The NBT classes implement IErrorable:
const nbt = new NbtBinary();
nbt.fromBinary(bytes, true, false, 8);

if (nbt.isInErrorState) {
  console.error('NBT parsing errors:');
  for (const error of nbt.errorMessages || []) {
    console.error(`  ${error.message}`);
    if (error.context) {
      console.error(`    Context: ${error.context}`);
    }
  }
}

Build docs developers (and LLMs) love