Wiki 9Minecraft Wiki 9Minecraft

Introduction to the Bedrock Scripting API

Get started with JavaScript and TypeScript inside Minecraft Bedrock behavior packs.

6Sections
3Code examples
3Known errors

The Bedrock Scripting API lets you run JavaScript directly inside a behavior pack to respond to world events, manipulate entities, and build custom game logic that JSON components alone cannot express. You reach for it when you need dynamic state, custom item or block interactions, server-side UI forms, or fine-grained control over ticks and entity behavior. Scripts run on the logical server, meaning they work across singleplayer worlds, Realms, and dedicated servers without requiring players to install external mods.

How Bedrock Scripting Works

Bedrock's modern scripting engine uses the QuickJS runtime to execute standard ES2020 JavaScript modules packaged inside your behavior pack. Unlike the retired legacy scripting engine from early Bedrock versions, the current GameTest-derived Scripting API is event-driven, strongly typed when paired with official TypeScript declarations, and split into distinct npm-style native modules.

Scripts only exist inside behavior packs (`BP/scripts/`). They do not run inside resource packs; if a script needs to trigger a visual or audio cue on the client, it calls server-side methods that instruct the client to play a sound, spawn a particle, or open a form. Every script execution happens within the context of a world tick, running at 20 ticks per second under normal load.

Core Script Modules

Functionality is divided into native modules that you declare in your pack's `manifest.json` and import at the top of your JavaScript files. Most add-ons only need the core server module, while others add UI or diagnostic capabilities.

Module NameWhat it doesNotes
@minecraft/serverExposes worlds, dimensions, entities, blocks, items, players, and world events.Has both stable releases (e.g., 1.8.0+) and `-beta` versions.
@minecraft/server-uiBuilds pop-up forms (`ActionFormData`, `ModalFormData`, `MessageFormData`) for players.Stable versions available; frequently paired with `@minecraft/server`.
@minecraft/server-gametestProvides automated test registration and simulated player helpers.Requires the Beta APIs experimental toggle in world settings.
@minecraft/server-netAllows HTTP requests from the server.Restricted to Bedrock Dedicated Server configurations.
@minecraft/server-adminManages server variables and configuration secrets.Restricted to Bedrock Dedicated Server configurations.

Configuring manifest.json for Scripts

To enable scripting in a behavior pack, your `manifest.json` requires two things beyond a standard data pack: a module entry with its `type` set to `"script"`, and a `dependencies` array listing the exact native modules and versions your code imports.

The `entry` field inside the script module points to the main JavaScript file relative to the behavior pack root, always starting inside the `scripts/` folder. You must generate fresh UUIDs for both the header and the module when setting up your own pack.

BP/manifest.json
{
  "format_version": 2,
  "header": {
    "name": "Starter Script Pack",
    "description": "Basic behavior pack configured for the Scripting API.",
    "uuid": "a183b410-5162-4c22-9111-111111111111",
    "version": [1, 0, 0],
    "min_engine_version": [1, 20, 80]
  },
  "modules": [
    {
      "description": "Script Module",
      "type": "script",
      "language": "javascript",
      "uuid": "b294c521-6273-4d33-8222-222222222222",
      "version": [1, 0, 0],
      "entry": "scripts/main.js"
    }
  ],
  "dependencies": [
    {
      "module_name": "@minecraft/server",
      "version": "1.10.0"
    }
  ]
}

Writing Your First Entry Script

Two top-level objects from `@minecraft/server` anchor almost every script: `world` and `system`. The `world` object gives you access to dimensions (`overworld`, `nether`, `the_end`), active players, and world-level events (`world.beforeEvents` and `world.afterEvents`). The `system` object handles the tick loop, scheduled timeouts (`system.run`, `system.runInterval`, `system.runTimeout`), and script lifecycle events.

The example below listens for an entity or player breaking a block and sends a message to everyone in the world, then sets up a repeating interval that runs once every 100 ticks (5 seconds).

BP/scripts/main.js
import { world, system } from "@minecraft/server";

world.afterEvents.playerBreakBlock.subscribe((event) => {
  const player = event.player;
  const blockId = event.brokenBlockPermutation.type.id;
  world.sendMessage(`${player.name} broke a ${blockId} block!`);
});

system.runInterval(() => {
  for (const player of world.getAllPlayers()) {
    player.onScreenDisplay.setActionBar(`Current Tick: ${system.currentTick}`);
  }
}, 100);

Before Events, After Events, and Read-Only Mode

World events are split into two namespaces: `world.beforeEvents` and `world.afterEvents`. A `before` event fires right as an action is about to happen, before the game state changes. Most `before` events expose a `.cancel = true` property on their event data object, letting you stop a block break, item use, or chat message from occurring.

Because `before` events run mid-tick while the engine is evaluating collisions and state transitions, the world is placed into a restricted read-only mode during their callbacks. If you try to spawn an entity, mutate a block, or modify a component inside a `before` event callback, the engine throws a privilege error. To perform world modifications in response to a `before` event, wrap the mutation inside `system.run(() => { ... })` so it defers execution until normal tick processing resumes. Callbacks inside `world.afterEvents` do not have this restriction.

BP/scripts/main.js
import { world, system } from "@minecraft/server";

world.beforeEvents.playerBreakBlock.subscribe((event) => {
  if (event.block.typeId === "minecraft:diamond_ore") {
    event.cancel = true;

    system.run(() => {
      event.player.sendMessage("You cannot mine diamond ore directly!");
      event.player.playSound("note.bass");
    });
  }
});

Enabling the Content Log for Debugging

Silent failures are the biggest hurdle when writing your first script. Before testing a pack, open Minecraft's Settings, navigate to the Creator section, and enable both "Enable Content Log File" and "Enable Content Log GUI".

With these enabled, any syntax errors, manifest mismatches, or runtime exceptions thrown by your code will immediately appear on screen, and `console.warn()` calls in your JavaScript will print directly to the log output.

Which versions this applies to

Applies to Minecraft Bedrock 1.20.0 and newer using format_version 2 manifests. Stable module versions (such as "1.10.0") work out of the box with no experimental toggles required. Using a "-beta" version string (such as "1.11.0-beta") requires enabling the "Beta APIs" experimental toggle in the world settings.

When it does not work

What you seeWhyThe fix
Content Log shows: "Module [@minecraft/server] with version [1.x.0-beta] is not a valid dependency" or complains that experimental APIs are not enabled.Your manifest.json requests a `-beta` module version, or requests a stable version number that is newer than the version of Minecraft you are currently running.Either enable the "Beta APIs" toggle under Experiments in the world settings (if you need beta features), or change the dependency version in `BP/manifest.json` to a stable version supported by your game client (for example, `"1.10.0"` on Bedrock 1.20.80).
Runtime error in Content Log: "ReferenceError: CannotModifyWorldError" or "function doesn't have privilege to call in read-only mode".You called a state-changing method (like `dimension.spawnEntity`, `block.setType`, or `entity.kill`) directly inside a `world.beforeEvents` callback.Wrap the state-changing calls inside `system.run(() => { ... })` so the game executes them on the normal tick phase after the before-event completes.
Pack loads into the world with zero errors, but nothing in `main.js` ever runs.The `entry` path in `manifest.json` does not match the actual file location on disk, or the file has a `.ts` extension instead of `.js` because TypeScript was not compiled.Ensure `entry` in `BP/manifest.json` is set to `"scripts/main.js"` and that the compiled JavaScript file actually sits at `BP/scripts/main.js`.

Reference, straight from the game files

Read out of the game itself, so the ids match the version you are building for.