File Structure and Manifest Declarations
At the pack level, your scripts are resources managed entirely within the Behavior Pack (`BP`). Unlike textures or geometries, scripts never go into the Resource Pack (`RP`), even when they control client-side UI forms via `@minecraft/server-ui`. All JavaScript files must reside inside the `BP/scripts/` directory. The game's scripting engine loads a single entry-point file defined in your `manifest.json`, and from that entry point, you use standard ES6 `import` statements to load secondary files.
To allocate the scripting environment for your pack, your `manifest.json` requires a module of type `script` and explicit module dependencies. The `entry` field is always written relative to the root of the Behavior Pack, not the `scripts` folder itself. Every built-in API module you wish to import in your code must first be declared by name and version inside the `dependencies` array, or the engine will refuse to resolve the import at runtime.
{
"format_version": 2,
"header": {
"name": "Resource Monitor Pack",
"description": "Demonstrates script resource setup",
"uuid": "a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d",
"version": [1, 0, 0],
"min_engine_version": [1, 20, 80]
},
"modules": [
{
"type": "script",
"language": "javascript",
"uuid": "f1e2d3c4-b5a6-4f5e-8d9c-0b1a2f3e4d5c",
"entry": "scripts/main.js",
"version": [1, 0, 0]
}
],
"dependencies": [
{
"module_name": "@minecraft/server",
"version": "1.10.0"
}
]
}The Sandboxed Runtime Environment
When a world loads with your Behavior Pack, Bedrock spins up an isolated JavaScript context powered by QuickJS. This environment is heavily sandboxed to protect players and servers. You do not have access to a Node.js environment, browser DOM objects, the local file system, or raw network sockets. Global functions you might expect from web development—such as `setTimeout`, `setInterval`, `fetch`, or `XMLHttpRequest`—do not exist unless you are running on a Bedrock Dedicated Server configured with specific server-only modules.
Because the environment lacks standard timers, all time-based resource scheduling is tied directly to the game loop through the `system` object in `@minecraft/server`. Each Behavior Pack runs in its own isolated context. Two different add-ons loaded into the same world cannot share JavaScript variables or import files from each other's `BP/scripts/` directories; if they need to communicate, they must pass data through the game world using dynamic properties, scoreboard objectives, or `system.sendScriptEvent`.
Execution Budgets and the Watchdog
Minecraft Bedrock runs at a target rate of 20 ticks per second, giving the entire game 50 milliseconds to process a single tick. Your scripts execute synchronously on the server thread during a specific slice of that tick. To prevent poorly written scripts from freezing the game, Bedrock employs a script watchdog that monitors both CPU time and memory allocation per Behavior Pack.
If a single tick in your script takes too long to execute—typically caused by an infinite `while` loop, pathfinding calculations in pure JS, or iterating over thousands of blocks at once—the watchdog will step in. Depending on the severity and world settings, the watchdog will either log a spike warning or immediately terminate your script module for the remainder of the session. Similarly, each script context has a strict memory ceiling; allocating massive arrays or retaining references to thousands of entity objects across ticks will trigger an out-of-memory shutdown.
| Resource Type | Constraint Behavior | How to Manage It |
|---|---|---|
| CPU Time (Per Tick) | Monitored by the watchdog. Single-tick spikes cause lag warnings; sustained hangs terminate the script. | Break heavy loops across multiple ticks using system.runJob(). |
| RAM / Heap Memory | Hard cap per Behavior Pack context. Exceeding the limit instantly kills the script module. | Avoid caching Entity or Block references in global arrays; store IDs or locations instead. |
| Dynamic Properties | String, number, boolean, or Vector3 values persisted to the world save. | Clear unused properties with setDynamicProperty(id, undefined) to keep save data clean. |
Spreading Work with Job Queues
When you must perform a resource-heavy task, such as scanning a large volume of blocks or initializing hundreds of entities, you should never run the entire loop inside a single `system.run` or event callback. Instead, use `system.runJob()`, which accepts a JavaScript generator function (`function*`).
Inside the generator, you place a `yield` statement inside your loop. Every time the execution hits `yield`, the script engine pauses your function and checks how much time is left in the current tick's budget. If time remains, it continues immediately; if the tick budget is exhausted, it suspends the generator and resumes it seamlessly on the next game tick. This allows you to process tens of thousands of operations without ever tripping the watchdog or dropping the server's tick rate.
import { world, system } from "@minecraft/server";
function* clearVolumeGenerator(dimension, startLoc, size) {
for (let x = 0; x < size; x++) {
for (let y = 0; y < size; y++) {
for (let z = 0; z < size; z++) {
const block = dimension.getBlock({
x: startLoc.x + x,
y: startLoc.y + y,
z: startLoc.z + z
});
if (block && !block.isAir) {
block.setType("minecraft:air");
}
// Yield control back to the engine so it can manage the tick budget
yield;
}
}
}
}
world.afterEvents.itemUse.subscribe((event) => {
if (event.itemStack.typeId === "minecraft:stick") {
const player = event.source;
system.runJob(clearVolumeGenerator(player.dimension, player.location, 16));
}
});Calling Vanilla Resources from Script
Although scripts live in the Behavior Pack, they frequently orchestrate assets that live in the Resource Pack—such as sounds, particle emitters, animations, and custom UI textures. You do not import RP files into your JavaScript code. Instead, you trigger them by passing their string identifiers into `@minecraft/server` methods.
For example, calling `dimension.spawnParticle("custom:magic_smoke", location)` or `player.playSound("custom.spell.cast")` sends a network packet from the server-side script engine to the client, instructing the client's Resource Pack to look up and render that specific identifier. Because the script engine has no visibility into the Resource Pack's files, it cannot validate whether those identifiers actually exist; if a sound or particle string is misspelled in your script, the function will execute without throwing a JavaScript error, and nothing will appear in-game.
Which versions this applies to
Applies to Minecraft Bedrock 1.20.0 and newer. The core manifest setup, `system.runJob`, and `dynamicProperties` are part of the stable `@minecraft/server` API (1.4.0+) and do not require the Beta APIs experimental toggle unless you specify a `-beta` version in your manifest dependencies.
When it does not work
| What you see | Why | The fix |
|---|---|---|
| [Scripting][error]-Script hang detected! Shutting down script module... | Your script ran a synchronous loop or heavy calculation that exceeded the watchdog's single-tick millisecond limit. | Convert your `for` or `while` loop into a generator function (`function*`), place `yield;` inside the loop body, and execute it with `system.runJob()`. |
| ReferenceError: 'setTimeout' is not defined | You tried to use standard browser or Node.js timing functions, which do not exist in Bedrock's sandboxed QuickJS environment. | Import `system` from `@minecraft/server` and use `system.runTimeout(callback, tickDelay)` or `system.runInterval(callback, tickInterval)` instead. |
| Module [@minecraft/server] with version [1.x.0-beta] is not available | Your `manifest.json` requests a `-beta` module version, or a version higher than the current game client supports, without the proper world settings. | Either enable the "Beta APIs" experimental toggle in the world settings, or change the version string in `manifest.json` to a stable release number such as `1.10.0`. |
| Failed to load entry point 'scripts/index.js' | The path specified in the `entry` field of your `manifest.json` does not match the actual file name or folder structure in your Behavior Pack, or TypeScript files were not compiled to `.js`. | Ensure the compiled `.js` file exists at the exact path relative to the `BP` root, and verify all relative `import` paths inside your scripts include the `.js` file extension. |
Reference, straight from the game files
Read out of the game itself, so the ids match the version you are building for.
