Minecraft Server ScriptAPI
Workflow
- Scope: Identify task (events, entities, components, commands, timing). Default to latest stable version unless beta/preview requested.
- Docs: Navigate from module index to specific class/interface/event. Use Microsoft Learn as source of truth. Search via MCP when details missing.
- Output: Quote exact API names. Provide minimal working example with required imports. Verify no official equivalent exists before creating custom helpers.
- For enums like
CustomCommandParamType, always check Microsoft Learn to confirm availability.
@minecraft/vanilla-data is not on Microsoft Learn, so skip MCP searches for those enums.
Common patterns
Events
- Subscribe/unsubscribe on
world/system events. Guard logic for performance.
Dimensions
- Use
world.getDimension(MinecraftDimensionTypes.<Dimension>) and pick the appropriate dimension for the task.
Components
- Check existence before access.
- Use typed IDs:
EntityComponentTypes, BlockComponentTypes, ItemComponentTypes.
Scheduling
- Use
system.run, system.runTimeout, system.runInterval, system.runJob.
Identifiers
- MUST use
@minecraft/vanilla-data enums: MinecraftBlockTypes, MinecraftEntityTypes, MinecraftItemTypes, MinecraftDimensionTypes, MinecraftEffectTypes, potionEffect, potionDelivery, feature, enchantment, cooldownCategory, cameraPresets, biome.
- Custom IDs: Must include namespace prefix (e.g.,
example:cmd). One consistent prefix per addon.
Example:
import { world, system } from "@minecraft/server";
import { MinecraftDimensionTypes, MinecraftBlockTypes } from "@minecraft/vanilla-data";
system.runInterval(() => {
const dimensions = [MinecraftDimensionTypes.Overworld, MinecraftDimensionTypes.Nether];
const blocks = [MinecraftBlockTypes.Stone, MinecraftBlockTypes.Sand, MinecraftBlockTypes.GrassBlock];
for (const dimension of dimensions) {
for (const block of blocks) {
world.getDimension(dimension).setBlockType({ x: 0, y: 0, z: 0 }, block);
}
}
});
Permission modes
Read-only
- Before simulation/events/tick start. No world mutations.
- Fix: defer to
system.run/runTimeout/runJob.
Doc verification
- When using any method/property, always check Microsoft Learn to confirm whether it is read-only safe or early-execution safe.
- Look for explicit notes in the API reference (read-only / early-execution) and follow them strictly.
- For arrow-function callbacks only, also check for restricted-execution notes like:
- "This closure is called with restricted-execution privilege."
- "This function can't be called in restricted-execution mode."
- If a restricted-execution note applies, review the arrow-function body to ensure no read-only or early-execution violations; defer with
system.run if needed.
Example (read-only deferral):
world.beforeEvents.playerInteractWithBlock.subscribe((event) => {
const player = event.player;
system.run(() => {
player.runCommand("say ok");
});
});
Early-execution
- Before world loads. Many APIs unavailable.
- Fix: defer to
world.afterEvents.worldLoad or system.run.
- Subscribe at root to avoid missing events.
Safe in early-execution:
- Event subscriptions (
world/system beforeEvents/afterEvents)
system.clearJob, clearRun, run, runInterval, runJob, runTimeout, waitTicks
BlockComponentRegistry.registerCustomComponent, ItemComponentRegistry.registerCustomComponent
Custom commands
- Interface:
CustomCommand (name, description, permissionLevel, mandatoryParameters, optionalParameters).
- Parameters:
CustomCommandParameter (name, type, optional enumName).
- Param types and arrow-function argument types:
String -> String
PlayerSelector -> Player
Location -> Vector3
ItemType -> ItemType
Integer -> Number
Float -> Number
Enum -> String
EntityType -> EntityType
EntitySelector -> Entity
Boolean -> Bool
BlockType -> BlockType
- Register enums:
CustomCommandRegistry.registerEnum(name, values).
- Register cmd:
CustomCommandRegistry.registerCommand(customCommand, callback).
- Callback:
(origin, ...args) => CustomCommandResult.
- Custom command callbacks run with restricted-execution privileges, so do not call read-only-restricted methods directly; defer with
system.run if needed.
- When using an arrow function, align parameter names and order with the
CustomCommandParameter.name list; avoid mismatched names or generic args when parameters are defined.
Example:
import {
system,
StartupEvent,
CommandPermissionLevel,
CustomCommandParamType,
CustomCommandStatus,
} from "@minecraft/server";
system.beforeEvents.startup.subscribe((init: StartupEvent) => {
init.customCommandRegistry.registerEnum("example:mode", ["on", "off"]);
init.customCommandRegistry.registerCommand(
{
name: "example:demo",
description: "Command demo",
permissionLevel: CommandPermissionLevel.GameDirectors,
cheatsRequired: true,
mandatoryParameters: [
{ type: CustomCommandParamType.String, name: "msg", },
{ type: CustomCommandParamType.Enum, name: "example:mode"},
],
optionalParameters: [
{ type: CustomCommandParamType.Boolean, name: "silent" },
{ type: CustomCommandParamType.Integer, name: "count" },
],
},
(origin, msg, mode, silent, count) => {
const msgValue = String(msg ?? "ok");
const modeValue = String(mode ?? "off");
const silentValue = Boolean(silent ?? false);
const countValue = Number(count ?? 1);
return {
status: CustomCommandStatus.Success,
message: silentValue
? undefined
: `[${origin.sourceType}] ${msgValue} mode=${modeValue} count=${countValue}`,
};
}
);
});
Script events
- Send:
system.sendScriptEvent(id, message) (namespaced ID, payload string).
- Receive:
system.afterEvents.scriptEventReceive.subscribe(callback, options?).
- Event:
ScriptEventCommandMessageAfterEvent (id, message, sourceType, optional sourceEntity/sourceBlock/initiator).
- Filter:
ScriptEventMessageFilterOptions.namespaces.
Example:
import { system, world, ScriptEventSource } from "@minecraft/server";
system.afterEvents.scriptEventReceive.subscribe((event) => {
const { id, message, sourceType, initiator, sourceEntity, sourceBlock } = event;
if (id !== "example:say") return;
switch (sourceType) {
case ScriptEventSource.Block:
world.sendMessage(`sendBy:${sourceBlock?.typeId ?? "unknown"} ${message}`);
break;
case ScriptEventSource.Entity:
world.sendMessage(`sendBy:${sourceEntity?.typeId ?? "unknown"} ${message}`);
break;
case ScriptEventSource.NPCDialogue:
world.sendMessage(`sendBy:${initiator?.typeId ?? "unknown"} ${message}`);
break;
case ScriptEventSource.Server:
world.sendMessage(`sendBy:server ${message}`);
break;
}
});
Performance
- Expensive work in events: use
system.runJob(generator) to spread across ticks.
- Short-circuit before iterating large sets.
system.run variants
run: next tick.
runTimeout(cb, ticks): delay N ticks. 0 can cause tight loops if misused.
runInterval(cb, ticks): repeat every N ticks until clearRun.
runJob(generator): long-running work. Keep iterations small.
Type safety
- Use
typeId checks and verify component existence.
instanceof only for documented @minecraft/server classes.
Minimal templates
Event subscription:
world.afterEvents.playerJoin.subscribe((event) => {
const player = event.player;
});
Tick loop:
system.runInterval(() => {
// tick logic
}, 1);
Get dimension:
const overworld = world.getDimension(MinecraftDimensionTypes.Overworld);
Spawn entity:
const overworld = world.getDimension(MinecraftDimensionTypes.Overworld);
overworld.spawnEntity(MinecraftEntityTypes.Zombie, { x: 0, y: 80, z: 0 });
Give item:
const item = new ItemStack(MinecraftItemTypes.Diamond, 1);
player.getComponent("inventory")?.container?.addItem(item);
Apply effect:
player.addEffect(MinecraftEffectTypes.Speed, 200, { amplifier: 1 });
Get component (typed):
const health = entity.getComponent(EntityComponentTypes.Health);
Pitfalls
- Null components: Check existence before access.
- Heavy events: Use
system.runJob.
- Permissions: Avoid read-only/early-execution violations.
- Raw IDs: Use typed enums.
- Namespaces: One consistent prefix per addon.
MCP tools
1---2name: minecraft-server-scriptapi3description: Guidance for working with the Minecraft Bedrock Script API @minecraft/server module. Use when answering questions about server-side scripting, world/system APIs, events, entities, components, or module versions for Minecraft Creator ScriptAPI.4---56# Minecraft Server ScriptAPI78## Workflow9101. **Scope**: Identify task (events, entities, components, commands, timing). Default to latest stable version unless beta/preview requested.112. **Docs**: Navigate from module index to specific class/interface/event. Use Microsoft Learn as source of truth. Search via MCP when details missing.123. **Output**: Quote exact API names. Provide minimal working example with required imports. Verify no official equivalent exists before creating custom helpers.13 - For enums like `CustomCommandParamType`, always check Microsoft Learn to confirm availability.14 - `@minecraft/vanilla-data` is not on Microsoft Learn, so skip MCP searches for those enums.1516## Common patterns1718### Events19- Subscribe/unsubscribe on `world`/`system` events. Guard logic for performance.2021### Dimensions22- Use `world.getDimension(MinecraftDimensionTypes.<Dimension>)` and pick the appropriate dimension for the task.2324### Components25- Check existence before access.26- Use typed IDs: `EntityComponentTypes`, `BlockComponentTypes`, `ItemComponentTypes`.2728### Scheduling29- Use `system.run`, `system.runTimeout`, `system.runInterval`, `system.runJob`.3031### Identifiers32- **MUST use `@minecraft/vanilla-data` enums**: `MinecraftBlockTypes`, `MinecraftEntityTypes`, `MinecraftItemTypes`, `MinecraftDimensionTypes`, `MinecraftEffectTypes`, `potionEffect`, `potionDelivery`, `feature`, `enchantment`, `cooldownCategory`, `cameraPresets`, `biome`.33- **Custom IDs**: Must include namespace prefix (e.g., `example:cmd`). One consistent prefix per addon.3435Example:3637```ts38import { world, system } from "@minecraft/server";39import { MinecraftDimensionTypes, MinecraftBlockTypes } from "@minecraft/vanilla-data";4041system.runInterval(() => {42 const dimensions = [MinecraftDimensionTypes.Overworld, MinecraftDimensionTypes.Nether];43 const blocks = [MinecraftBlockTypes.Stone, MinecraftBlockTypes.Sand, MinecraftBlockTypes.GrassBlock];44 for (const dimension of dimensions) {45 for (const block of blocks) {46 world.getDimension(dimension).setBlockType({ x: 0, y: 0, z: 0 }, block);47 }48 }49});50```5152## Permission modes5354### Read-only55- Before simulation/events/tick start. No world mutations.56- Fix: defer to `system.run`/`runTimeout`/`runJob`.5758### Doc verification59- When using any method/property, always check Microsoft Learn to confirm whether it is read-only safe or early-execution safe.60- Look for explicit notes in the API reference (read-only / early-execution) and follow them strictly.61- For arrow-function callbacks only, also check for restricted-execution notes like:62 - "This closure is called with restricted-execution privilege."63 - "This function can't be called in restricted-execution mode."64- If a restricted-execution note applies, review the arrow-function body to ensure no read-only or early-execution violations; defer with `system.run` if needed.6566Example (read-only deferral):6768```ts69world.beforeEvents.playerInteractWithBlock.subscribe((event) => {70 const player = event.player;71 system.run(() => {72 player.runCommand("say ok");73 });74});75```7677### Early-execution78- Before world loads. Many APIs unavailable.79- Fix: defer to `world.afterEvents.worldLoad` or `system.run`.80- Subscribe at root to avoid missing events.8182**Safe in early-execution**:83- Event subscriptions (`world`/`system` `beforeEvents`/`afterEvents`)84- `system.clearJob`, `clearRun`, `run`, `runInterval`, `runJob`, `runTimeout`, `waitTicks`85- `BlockComponentRegistry.registerCustomComponent`, `ItemComponentRegistry.registerCustomComponent`8687## Custom commands8889- Interface: `CustomCommand` (`name`, `description`, `permissionLevel`, `mandatoryParameters`, `optionalParameters`).90- Parameters: `CustomCommandParameter` (`name`, `type`, optional `enumName`).91- Param types and arrow-function argument types:92 - `String` -> `String`93 - `PlayerSelector` -> `Player`94 - `Location` -> `Vector3`95 - `ItemType` -> `ItemType`96 - `Integer` -> `Number`97 - `Float` -> `Number`98 - `Enum` -> `String`99 - `EntityType` -> `EntityType`100 - `EntitySelector` -> `Entity`101 - `Boolean` -> `Bool`102 - `BlockType` -> `BlockType`103- Register enums: `CustomCommandRegistry.registerEnum(name, values)`.104- Register cmd: `CustomCommandRegistry.registerCommand(customCommand, callback)`.105- Callback: `(origin, ...args) => CustomCommandResult`.106- Custom command callbacks run with restricted-execution privileges, so do not call read-only-restricted methods directly; defer with `system.run` if needed.107- When using an arrow function, align parameter names and order with the `CustomCommandParameter.name` list; avoid mismatched names or generic `args` when parameters are defined.108109Example:110111```ts112import {113 system,114 StartupEvent,115 CommandPermissionLevel,116 CustomCommandParamType,117 CustomCommandStatus,118} from "@minecraft/server";119120system.beforeEvents.startup.subscribe((init: StartupEvent) => {121 init.customCommandRegistry.registerEnum("example:mode", ["on", "off"]);122123 init.customCommandRegistry.registerCommand(124 {125 name: "example:demo",126 description: "Command demo",127 permissionLevel: CommandPermissionLevel.GameDirectors,128 cheatsRequired: true,129 mandatoryParameters: [130 { type: CustomCommandParamType.String, name: "msg", },131 { type: CustomCommandParamType.Enum, name: "example:mode"},132 ],133 optionalParameters: [134 { type: CustomCommandParamType.Boolean, name: "silent" },135 { type: CustomCommandParamType.Integer, name: "count" },136 ],137 },138 (origin, msg, mode, silent, count) => {139 const msgValue = String(msg ?? "ok");140 const modeValue = String(mode ?? "off");141 const silentValue = Boolean(silent ?? false);142 const countValue = Number(count ?? 1);143144 return {145 status: CustomCommandStatus.Success,146 message: silentValue147 ? undefined148 : `[${origin.sourceType}] ${msgValue} mode=${modeValue} count=${countValue}`,149 };150 }151 );152});153```154155## Script events156157- Send: `system.sendScriptEvent(id, message)` (namespaced ID, payload string).158- Receive: `system.afterEvents.scriptEventReceive.subscribe(callback, options?)`.159- Event: `ScriptEventCommandMessageAfterEvent` (`id`, `message`, `sourceType`, optional `sourceEntity`/`sourceBlock`/`initiator`).160- Filter: `ScriptEventMessageFilterOptions.namespaces`.161162Example:163164```ts165import { system, world, ScriptEventSource } from "@minecraft/server";166167system.afterEvents.scriptEventReceive.subscribe((event) => {168 const { id, message, sourceType, initiator, sourceEntity, sourceBlock } = event;169 if (id !== "example:say") return;170171 switch (sourceType) {172 case ScriptEventSource.Block:173 world.sendMessage(`sendBy:${sourceBlock?.typeId ?? "unknown"} ${message}`);174 break;175 case ScriptEventSource.Entity:176 world.sendMessage(`sendBy:${sourceEntity?.typeId ?? "unknown"} ${message}`);177 break;178 case ScriptEventSource.NPCDialogue:179 world.sendMessage(`sendBy:${initiator?.typeId ?? "unknown"} ${message}`);180 break;181 case ScriptEventSource.Server:182 world.sendMessage(`sendBy:server ${message}`);183 break;184 }185});186187```188189## Performance190191- Expensive work in events: use `system.runJob(generator)` to spread across ticks.192- Short-circuit before iterating large sets.193194## system.run variants195196- `run`: next tick.197- `runTimeout(cb, ticks)`: delay N ticks. `0` can cause tight loops if misused.198- `runInterval(cb, ticks)`: repeat every N ticks until `clearRun`.199- `runJob(generator)`: long-running work. Keep iterations small.200201## Type safety202203- Use `typeId` checks and verify component existence.204- `instanceof` only for documented `@minecraft/server` classes.205206207208## Minimal templates209210Event subscription:211212```ts213world.afterEvents.playerJoin.subscribe((event) => {214 const player = event.player;215});216```217218Tick loop:219220```ts221system.runInterval(() => {222 // tick logic223}, 1);224```225226Get dimension:227228```ts229const overworld = world.getDimension(MinecraftDimensionTypes.Overworld);230```231232Spawn entity:233234```ts235const overworld = world.getDimension(MinecraftDimensionTypes.Overworld);236overworld.spawnEntity(MinecraftEntityTypes.Zombie, { x: 0, y: 80, z: 0 });237```238239Give item:240241```ts242const item = new ItemStack(MinecraftItemTypes.Diamond, 1);243player.getComponent("inventory")?.container?.addItem(item);244```245246Apply effect:247248```ts249player.addEffect(MinecraftEffectTypes.Speed, 200, { amplifier: 1 });250```251252Get component (typed):253254```ts255const health = entity.getComponent(EntityComponentTypes.Health);256```257258## Pitfalls259260- **Null components**: Check existence before access.261- **Heavy events**: Use `system.runJob`.262- **Permissions**: Avoid read-only/early-execution violations.263- **Raw IDs**: Use typed enums.264- **Namespaces**: One consistent prefix per addon.265266## MCP tools267268- Endpoint: https://learn.microsoft.com/api/mcp269- Search: `microsoft_docs_search` (module + class + task).270- Fetch: `microsoft_docs_fetch` (full signatures/details).