Skip to main content
Minecraft Creator Tools provides comprehensive support for reading, modifying, and creating Minecraft worlds through the MCWorld class and NBT parsing utilities.

World Architecture

Minecraft worlds consist of several key components:
World Structure
├── level.dat (NBT format - world metadata)
├── level.dat_old (Backup)
├── levelname.txt (World name)
├── db/ (LevelDB - chunk data)
├── manifest.json (World metadata)
├── world_icon.jpeg (World thumbnail)
├── behavior_packs/ (Embedded packs)
└── resource_packs/ (Embedded packs)
Reference: app/src/minecraft/MCWorld.ts:1

NBT (Named Binary Tag) Format

NBT is Minecraft’s proprietary format for hierarchical typed data.

Reading NBT Data

import NbtBinary from "./minecraft/NbtBinary";

const nbt = new NbtBinary();

// Parse NBT from bytes (little-endian for Bedrock)
nbt.fromBinary(
  levelDatBytes,
  true,  // littleEndian
  false, // isVarint
  0,     // skipBytes
  true   // stringsAreASCII
);

// Access the root tag
const root = nbt.singleRoot;
if (root) {
  const spawnX = root.find("SpawnX")?.value;
  const spawnY = root.find("SpawnY")?.value;
  const spawnZ = root.find("SpawnZ")?.value;
}

NBT Tag Types

enum NbtTagType {
  end = 0,
  byte = 1,
  short = 2,
  int = 3,
  long = 4,
  float = 5,
  double = 6,
  byteArray = 7,
  string = 8,
  list = 9,
  compound = 10,
  intArray = 11,
  longArray = 12
}

Working with NBT Tags

// Find a tag by name
const tag = root.find("LevelName");

if (tag) {
  // Get value with type checking
  const name = tag.valueAsString;
  
  // Or use type-specific accessors
  const intValue = tag.valueAsInt;
  const bigIntValue = tag.valueAsBigInt;
  
  // Get child tags from compound
  const children = tag.getTagChildren();
}

// Convert to JSON
const json = nbt.getJsonString();
console.log(json);

// Convert back to binary
const bytes = nbt.toBinary();
Reference: app/src/minecraft/NbtBinary.ts:1

Working with MCWorld

Loading a World

1

Create MCWorld instance

import MCWorld from "./minecraft/MCWorld";

// From a file (.mcworld)
const mcworld = await MCWorld.ensureOnFile(worldFile, project);

// From a folder
const mcworld = await MCWorld.ensureMCWorldOnFolder(worldFolder, project);
2

Load metadata

// Load level.dat and other meta files
await mcworld.loadMetaFiles(false);

// Check if loaded
if (mcworld.isLoaded) {
  console.log(`World loaded: ${mcworld.name}`);
}
3

Access world properties

// Basic properties
console.log(`Spawn: ${mcworld.spawnX}, ${mcworld.spawnY}, ${mcworld.spawnZ}`);
console.log(`Game type: ${mcworld.levelData?.gameType}`);
console.log(`Difficulty: ${mcworld.levelData?.difficulty}`);

// Experiments
console.log(`Beta APIs: ${mcworld.betaApisExperiment}`);
console.log(`Deferred Preview: ${mcworld.deferredTechnicalPreviewExperiment}`);

Modifying World Properties

// Set spawn point
mcworld.spawnX = 100;
mcworld.spawnY = 64;
mcworld.spawnZ = 200;

// Change world name
mcworld.name = "My Custom World";

// Enable experiments
mcworld.betaApisExperiment = true;
mcworld.deferredTechnicalPreviewExperiment = true;
mcworld.dataDrivenItemsExperiment = true;

// Modify level data directly
if (mcworld.levelData) {
  mcworld.levelData.gameType = 1; // Creative
  mcworld.levelData.difficulty = 0; // Peaceful
}

// Save changes
await mcworld.save();

World Settings

Apply comprehensive settings to a world:
import { Generator } from "./minecraft/WorldLevelDat";

const worldSettings = {
  generator: Generator.flat,      // World type
  betaApisExperiment: true,      // Enable beta APIs
  seed: "minecraft",             // World seed
  gameType: 1,                   // 0=Survival, 1=Creative, 2=Adventure
  difficulty: 2,                 // 0=Peaceful, 1=Easy, 2=Normal, 3=Hard
};

await mcworld.applyWorldSettings(worldSettings);
  • Generator.default - Default terrain
  • Generator.flat - Flat world
  • Generator.infinite - Infinite world
  • Generator.legacy - Legacy terrain
  • Generator.void - Void/empty world

Pack Management

Adding Behavior Packs

// Add a behavior pack to the world
const wasAdded = mcworld.ensureBehaviorPack(
  "550e8400-e29b-41d4-a716-446655440000", // Pack UUID
  [1, 0, 0],                                 // Version
  "My Behavior Pack",                        // Pack name
  0                                          // Priority (optional)
);

if (wasAdded) {
  console.log("Behavior pack added");
}

// Check if pack exists
const bp = mcworld.getBehaviorPack("550e8400-e29b-41d4-a716-446655440000");
if (bp) {
  console.log(`Pack version: ${bp.version.join('.')}`);
}

Adding Resource Packs

// Add a resource pack
mcworld.ensureResourcePack(
  "660e8400-e29b-41d4-a716-446655440001",
  [1, 0, 0],
  "My Resource Pack",
  0
);

// Add multiple packs from strings
const packString = "uuid1:1.0.0,uuid2:2.1.0";
mcworld.ensureBehaviorPacksFromString(packString);
mcworld.ensureResourcePacksFromString(packString);

Pack References

Add complete pack reference sets:
interface IPackageReference {
  name: string;
  behaviorPackReferences?: Array<{
    uuid: string;
    version: number[];
    priority?: number;
  }>;
  resourcePackReferences?: Array<{
    uuid: string;
    version: number[];
    priority?: number;
  }>;
}

const packRefSet: IPackageReference = {
  name: "My Add-on",
  behaviorPackReferences: [{
    uuid: "550e8400-e29b-41d4-a716-446655440000",
    version: [1, 0, 0],
    priority: 0
  }],
  resourcePackReferences: [{
    uuid: "660e8400-e29b-41d4-a716-446655440001",
    version: [1, 0, 0],
    priority: 0
  }]
};

mcworld.ensurePackReferenceSet(packRefSet);

LevelDB and Chunk Data

For advanced world manipulation, you can work with chunk data:

Loading LevelDB

// Load the LevelDB database
const loaded = await mcworld.loadLevelDb(false, {
  maxNumberOfRecordsToProcess: 10000,
  progressCallback: (phase, current, total) => {
    console.log(`${phase}: ${current}/${total}`);
  }
});

if (loaded) {
  console.log(`Loaded ${mcworld.chunkCount} chunks`);
}
Loading LevelDB can be memory-intensive for large worlds. Use progress callbacks and consider cleanup options.

Working with Chunks

// Get block at location
const block = mcworld.getBlock(
  new BlockLocation(100, 64, 200),
  0 // dimension: 0=overworld, 1=nether, 2=end
);

console.log(`Block type: ${block.type}`);

// Get top block at X,Z
const topBlock = mcworld.getTopBlock(100, 200);
const topY = mcworld.getTopBlockY(100, 200);

console.log(`Top block at (100, 200): ${topBlock?.type} at Y=${topY}`);

Iterating Chunks

// Process all chunks
await mcworld.forEachChunk(
  async (chunk, x, z, dimension) => {
    console.log(`Processing chunk at ${x}, ${z} in dimension ${dimension}`);
    
    // Access chunk data
    const topBlock = chunk.getTopBlock(8, 8); // Middle of chunk
    
    // Process blocks, entities, etc.
  },
  {
    dimensionFilter: 0,           // Only overworld
    clearCacheAfterProcess: true, // Free memory
    progressCallback: async (processed, total) => {
      console.log(`Processed ${processed}/${total} chunks`);
    }
  }
);

Memory Management

Large worlds consume significant memory. Use these methods to manage it:
// Clear parsed data but keep raw bytes (recommended)
// Chunks can be re-parsed if needed
mcworld.clearAllChunkCaches();

// Clear LevelDB data after processing
// WARNING: Cannot reload after this
mcworld.clearLevelDbData();

// Clear all chunk data (most aggressive)
// WARNING: Chunks cannot be accessed after this
mcworld.clearAllChunkData();
Best practice: Use clearCacheAfterProcess: true in forEachChunk for automatic cleanup.

Creating New Worlds

Generate a Fresh World

async function createWorld(project: Project) {
  const mcworld = new MCWorld();
  mcworld.project = project;
  
  // Ensure storage (ZIP format)
  mcworld.ensureZipStorage();
  
  // Set up level data
  const levelDat = mcworld.ensureLevelData();
  levelDat.ensureDefaults();
  
  // Configure world
  mcworld.name = "My New World";
  mcworld.spawnX = 0;
  mcworld.spawnY = 64;
  mcworld.spawnZ = 0;
  
  // Apply settings
  await mcworld.applyWorldSettings({
    generator: Generator.flat,
    betaApisExperiment: true,
    gameType: 1,
    difficulty: 0
  });
  
  // Add packs
  mcworld.ensureBehaviorPack(
    project.defaultBehaviorPackUniqueId,
    project.defaultBehaviorPackVersion,
    project.name
  );
  
  // Save
  await mcworld.save();
  
  // Export as .mcworld
  const bytes = await mcworld.getBytes();
  return bytes;
}

Clone and Modify Existing World

async function cloneWorld(
  sourceWorld: MCWorld,
  newName: string
) {
  // Load source
  await sourceWorld.loadMetaFiles(false);
  
  // Create new world
  const newWorld = new MCWorld();
  newWorld.ensureZipStorage();
  
  // Copy to new storage
  const targetFolder = newWorld.effectiveRootFolder;
  if (targetFolder) {
    await sourceWorld.copyAsFolderTo(targetFolder);
  }
  
  // Load and modify
  await newWorld.loadMetaFiles(false);
  newWorld.name = newName;
  
  // Save
  await newWorld.save();
  
  return newWorld;
}

Practical Examples

Example 1: World Information Tool

async function getWorldInfo(worldFile: IFile) {
  const mcworld = await MCWorld.ensureOnFile(worldFile);
  await mcworld.loadMetaFiles(false);
  
  const info = {
    name: mcworld.name,
    spawn: {
      x: mcworld.spawnX,
      y: mcworld.spawnY,
      z: mcworld.spawnZ
    },
    gameType: mcworld.levelData?.gameType,
    difficulty: mcworld.levelData?.difficulty,
    experiments: {
      betaApis: mcworld.betaApisExperiment,
      deferredPreview: mcworld.deferredTechnicalPreviewExperiment,
      dataDrivenItems: mcworld.dataDrivenItemsExperiment
    },
    behaviorPacks: mcworld.worldBehaviorPacks?.length ?? 0,
    resourcePacks: mcworld.worldResourcePacks?.length ?? 0
  };
  
  return info;
}

Example 2: Bulk Pack Injection

async function injectPacks(
  worldFile: IFile,
  packs: Array<{ type: 'behavior' | 'resource', uuid: string, version: number[], name: string }>
) {
  const mcworld = await MCWorld.ensureOnFile(worldFile);
  await mcworld.loadMetaFiles(false);
  
  for (const pack of packs) {
    if (pack.type === 'behavior') {
      mcworld.ensureBehaviorPack(pack.uuid, pack.version, pack.name);
    } else {
      mcworld.ensureResourcePack(pack.uuid, pack.version, pack.name);
    }
  }
  
  await mcworld.save();
  
  // Save back to file
  const bytes = await mcworld.getBytes();
  if (bytes) {
    worldFile.setContent(bytes);
    await worldFile.saveContent();
  }
}

Example 3: World Migration

async function migrateWorld(
  oldWorld: MCWorld,
  newWorldSettings: IWorldSettings
) {
  // Load old world
  await oldWorld.loadMetaFiles(false);
  
  // Preserve important data
  const oldPacks = {
    behavior: oldWorld.worldBehaviorPacks || [],
    resource: oldWorld.worldResourcePacks || []
  };
  
  // Apply new settings
  await oldWorld.applyWorldSettings(newWorldSettings);
  
  // Restore pack references
  oldWorld.worldBehaviorPacks = oldPacks.behavior;
  oldWorld.worldResourcePacks = oldPacks.resource;
  
  // Save migrated world
  await oldWorld.save();
  
  console.log("World migrated successfully");
}

Example 4: Extract World Statistics

async function analyzeWorld(worldFolder: IFolder) {
  const mcworld = await MCWorld.ensureMCWorldOnFolder(worldFolder);
  await mcworld.loadLevelDb(false);
  
  const stats = {
    totalChunks: mcworld.chunkCount,
    dimensions: {} as Record<number, number>,
    regions: mcworld.regionsByDimension,
    bounds: {
      minX: mcworld.minX,
      maxX: mcworld.maxX,
      minZ: mcworld.minZ,
      maxZ: mcworld.maxZ
    }
  };
  
  // Count chunks per dimension
  for (const [dim, xMap] of mcworld.chunks.entries()) {
    let count = 0;
    for (const zMap of xMap.values()) {
      count += zMap.size;
    }
    stats.dimensions[dim] = count;
  }
  
  // Clean up memory
  mcworld.clearAllChunkCaches();
  
  return stats;
}

Best Practices

Load metadata first

Always call loadMetaFiles() before accessing world properties.

Save after modifications

Call save() after changing world properties or pack references.

Manage memory

Use cache clearing for large worlds to prevent memory issues.

Validate pack UUIDs

Ensure pack UUIDs are valid and unique before adding to worlds.

Troubleshooting

Check:
  • level.dat exists and is valid NBT
  • File is loaded (await file.loadContent())
  • File content is Uint8Array for .mcworld files
  • NBT parsing errors in error messages
Ensure:
  • await mcworld.save() is called
  • Storage is saved (await storage.rootFolder.saveAll())
  • For file-based worlds, save to file after getBytes()
Verify:
  • Pack UUIDs match manifest.json exactly
  • Pack versions are valid arrays [major, minor, patch]
  • Both pack registration and history are updated
  • Experiments are enabled if packs require them
Use memory management:
// Process chunks with auto-cleanup
await mcworld.forEachChunk(
  async (chunk) => { /* process */ },
  { clearCacheAfterProcess: true }
);

// Clear when done
mcworld.clearLevelDbData();
Set all three coordinates:
mcworld.spawnX = 0;
mcworld.spawnY = 64;
mcworld.spawnZ = 0;
await mcworld.save();

Build docs developers (and LLMs) love