Manifest Setup and Module Dependencies
Before the game will run any script files, your behavior pack's manifest.json must declare a script module and link to the engine's built-in API modules. The module entry requires a type of script, a language set to javascript, and an entry field pointing to your main file inside the pack's scripts folder. Even if you write your code in TypeScript, you must compile it down to plain JavaScript before the game can load it.
You also need a dependencies array that names the specific API modules your code imports, most commonly @minecraft/server. Version strings in the dependencies block can be stable numbers like 1.12.0, or beta strings like 1.13.0-beta. Stable versions run in any world without extra settings, while beta versions grant access to unfinished features but require the Beta APIs world toggle to be enabled.
{
"format_version": 2,
"header": {
"name": "Core Script Example",
"description": "Behavior pack with @minecraft/server setup",
"uuid": "a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d",
"version": [1, 0, 0],
"min_engine_version": [1, 21, 0]
},
"modules": [
{
"type": "script",
"language": "javascript",
"uuid": "f6e5d4c3-b2a1-4f5e-9d8c-7b6a5e4d3c2b",
"entry": "scripts/main.js",
"version": [1, 0, 0]
}
],
"dependencies": [
{
"module_name": "@minecraft/server",
"version": "1.12.0"
}
]
}Core Modules in the Scripting Ecosystem
The scripting environment is split into separate NPM-style packages so packs only load what they use. For standard gameplay mechanics, you will spend almost all of your time inside @minecraft/server and @minecraft/server-ui. Other specialized modules exist for automated testing or server administration, and each requires its own entry in your manifest dependencies.
| Module Name | What it does | Notes |
|---|---|---|
| @minecraft/server | Provides core access to worlds, dimensions, entities, blocks, items, and game events. | Has both stable releases and a rolling beta track. |
| @minecraft/server-ui | Builds pop-up dialog menus like ActionFormData, MessageFormData, and ModalFormData. | Stable versions available; halts player movement while a form is open. |
| @minecraft/server-gametest | Runs automated in-world tests using simulated players and structure blocks. | Requires the Beta APIs experimental toggle. |
| @minecraft/server-net | Allows outbound HTTP requests and basic network communication. | Exclusive to Bedrock Dedicated Server (BDS); will not load in standard worlds. |
| @minecraft/server-admin | Reads server configuration variables and secrets. | Exclusive to Bedrock Dedicated Server (BDS). |
The World and System Objects
Once you import @minecraft/server, almost every interaction begins with either the world object or the system object. The world object represents the loaded game session. Through it, you fetch dimensions (overworld, nether, the_end), query for active players or entities, broadcast chat messages, and read or write world-level dynamic properties.
The system object represents the script engine and the server's tick clock. Minecraft Bedrock runs at 20 ticks per second, and system provides the scheduling methods to run your code on that clock. Because standard JavaScript timers like setTimeout and setInterval do not exist in the Bedrock script environment, you must use system.run, system.runInterval, and system.runTimeout to delay or repeat logic. Both world and system also expose event registries where your script can subscribe to game hooks.
Before Events, After Events, and Read-Only Mode
Events in @minecraft/server are divided into two timing phases: beforeEvents and afterEvents. Understanding the difference between them is the most important part of writing bug-free Bedrock scripts. A beforeEvent fires right as an action is about to happen—such as a player swinging at a block or sending a chat message—before the engine calculates the outcome. In a beforeEvent callback, the event payload usually has a cancel property. Setting data.cancel = true aborts the action completely.
Because beforeEvents interrupt the middle of the game's internal tick calculation, the engine places your script into read-only mode while the callback runs. In read-only mode, you can inspect entities, blocks, and tags, and you can cancel the event itself, but you are forbidden from modifying the world state. Calling methods like entity.kill(), block.setType(), or player.teleport() inside a beforeEvent will immediately throw a privilege error.
To change the world in response to a beforeEvent, you have two choices: either listen to the corresponding afterEvent instead (which runs after the action finishes and allows full world modification), or wrap your state-changing code inside a system.run() call. Passing a function to system.run() queues it to execute at the end of the current tick, safely outside the read-only window.
import { world, system } from "@minecraft/server";
// Listening to a beforeEvent (Read-Only state)
world.beforeEvents.playerBreakBlock.subscribe((eventData) => {
const player = eventData.player;
const block = eventData.block;
if (block.typeId === "minecraft:diamond_ore") {
// Canceling is allowed directly inside a beforeEvent
eventData.cancel = true;
// Modifying world state (like spawning particles or damaging the player)
// must be deferred to the normal tick execution using system.run
system.run(() => {
player.sendMessage("You cannot mine diamond ore directly!");
player.dimension.spawnParticle("minecraft:villager_angry", block.location);
});
}
});
// Listening to an afterEvent (Read-Write state)
world.afterEvents.entityDie.subscribe((eventData) => {
const deadEntity = eventData.deadEntity;
if (deadEntity.typeId === "minecraft:zombie") {
// State changes work directly here without system.run
deadEntity.dimension.spawnEntity("minecraft:experience_orb", deadEntity.location);
}
});
// Running a repeating loop every 100 ticks (5 seconds)
system.runInterval(() => {
for (const player of world.getAllPlayers()) {
player.onScreenDisplay.setActionBar("Server tick: " + system.currentTick);
}
}, 100);Data Persistence and Entity Lifetime
JavaScript variables stored in memory only last as long as the world stays open. When the player saves and quits, or the server restarts, all script arrays, maps, and variables are wiped clean. To save data permanently across sessions without resorting to scoreboard tricks, use Dynamic Properties. You can call setDynamicProperty and getDynamicProperty on the world object, individual entities, or items to store strings, numbers, booleans, and Vector3 coordinates directly in the world save file.
When storing references to entities in your own script variables across multiple ticks, always remember that entities can die or unload when their chunk goes out of simulation distance. Calling methods on an unloaded or dead entity reference throws an error. Always call the entity.isValid() method before reading components or modifying an entity that you saved from an earlier tick.
Which versions this applies to
Applies to Minecraft Bedrock 1.20.0 and newer (1.21+ recommended for current stable module versions). Standard version strings in manifest.json work in any world; versions ending in '-beta' require the 'Beta APIs' experimental toggle enabled in the world settings.
When it does not work
| What you see | Why | The fix |
|---|---|---|
| Native function [Entity::kill] (or setType, teleport, addEffect) does not have required privileges. | You called a method that modifies the world state directly inside a beforeEvents callback, while the game engine is locked in read-only mode. | Wrap the world-modifying lines inside a system.run(() => { ... }) callback so they execute later in the tick, or switch your listener from world.beforeEvents to world.afterEvents if you do not need to cancel the event. |
| Module [@minecraft/server] with version [1.x.0-beta] could not be resolved (or pack fails to load scripts). | Either the 'Beta APIs' experimental toggle is turned off in the world settings while using a -beta dependency, or Minecraft updated to a new minor version and retired the older beta version string. | Enable 'Beta APIs' in the world's Experiments menu, and update the version string in BP/manifest.json to match the latest beta version supported by your current client, or switch to a stable non-beta version string. |
| Failed to load entry point 'scripts/main.js' or SyntaxError in content log. | The path in the manifest's 'entry' field does not match the actual file location, or you pointed it at a TypeScript (.ts) file instead of compiled JavaScript (.js). | Ensure the file exists at the exact path relative to the behavior pack root (for example, BP/scripts/main.js) and contains valid ES6 JavaScript. |
| Failed to read property or call method on an invalid Entity. | Your script saved an Entity object reference in a variable or closure, and tried to use it on a later tick after the entity died, despawned, or entered an unloaded chunk. | Check if (entity.isValid()) before accessing properties or calling methods on any entity reference held across ticks. |
Reference, straight from the game files
Read out of the game itself, so the ids match the version you are building for.
