~/dev.log/bedrock-events-commands

Two systems drive almost every Bedrock add-on: events (react to something that happened) and custom commands (let a player or another script trigger your logic on demand).

Common world events

import { world } from "@minecraft/server";

world.afterEvents.entityHurt.subscribe((e) => {
  console.warn(`${e.hurtEntity.typeId} took ${e.damage} damage`);
});

world.afterEvents.blockPlace.subscribe((e) => {
  if (e.block.typeId === "minecraft:diamond_block") {
    e.player.sendMessage("§bNice.");
  }
});

world.beforeEvents.chatSend.subscribe((e) => {
  if (e.message.startsWith("!")) {
    e.cancel = true; // swallow the chat message
    // handle it as a pseudo-command yourself
  }
});

beforeEvents run synchronously and can cancel/mutate; afterEvents are read-only notifications after the fact. Most gameplay logic belongs in afterEvents, beforeEvents are reserved for the handful of events that support cancellation.

Slash commands via /scriptevent

The cleanest way to expose a command players (or command blocks / functions) can call is system.afterEvents.scriptEventReceive, paired with a namespace so it doesn't collide with other add-ons:

import { system } from "@minecraft/server";

system.afterEvents.scriptEventReceive.subscribe((event) => {
  const { id, message, sourceEntity } = event;
  if (id !== "myaddon:kit") return;

  const kit = message; // e.g. "/scriptevent myaddon:kit starter"
  giveKit(sourceEntity, kit);
});

Register the namespace in manifest.json so /scriptevent autocompletes it:

"metadata": {
  "authors": ["you"],
  "namespace": "myaddon"
}

Custom commands (1.21+ @minecraft/server command registry)

Newer API versions let scripts register real slash commands (no /scriptevent prefix needed) via the experimental custom-commands API:

import { system, CustomCommandStatus, CustomCommandParamType } from "@minecraft/server";

system.beforeEvents.startup.subscribe(({ customCommandRegistry }) => {
  customCommandRegistry.registerCommand(
    {
      name: "myaddon:heal",
      description: "Heal yourself",
      permissionLevel: 0,
      optionalParameters: [{ name: "amount", type: CustomCommandParamType.Integer }]
    },
    (origin, amount) => {
      const player = origin.sourceEntity;
      player?.runCommand(`effect @s instant_health 1 ${amount ?? 1}`);
      return { status: CustomCommandStatus.Success };
    }
  );
});

This must run inside the startup event, command registration is only allowed before the world finishes loading.

Running vanilla commands from script

player.runCommand("give @s diamond 1");
// or, queued and error-safe across ticks:
system.run(() => player.runCommandAsync("give @s diamond 1"));

Prefer runCommandAsync for anything that might legally fail (e.g. /tp into an unloaded chunk) since it returns a promise instead of throwing synchronously.

A practical pattern: custom item with a triggered ability

import { world } from "@minecraft/server";

world.afterEvents.itemUse.subscribe((e) => {
  if (e.itemStack.typeId !== "myaddon:thunder_wand") return;
  const player = e.source;
  const loc = player.getViewDirection();
  player.dimension.createExplosion(
    { x: player.location.x + loc.x * 5, y: player.location.y, z: player.location.z + loc.z * 5 },
    2
  );
});

Next: Forms & UI.