Wiki 9Minecraft Wiki 9Minecraft

Custom Block Events and Custom Components

How to handle block interactions, ticks, and lifecycle triggers in Bedrock add-ons.

5Sections
3Code examples
4Known errors

Block events allow custom blocks to react when players interact with them, break them, step on them, or when they tick in the world. In modern Minecraft Bedrock, the older JSON-based event system has been replaced by Script API Custom Components. You reach for this system whenever a block needs dynamic behavior—such as a lamp toggling on right-click, a crop growing over time, or an ore spawning particles when mined.

The Shift from JSON Events to Custom Components

If you are looking at older add-on packs or maintaining legacy content, you will likely encounter an "events" object inside the block JSON file, paired with trigger components like "minecraft:on_interact" or "minecraft:on_player_destroyed". That entire JSON event system was part of the Holiday Creator Features experimental toggle.

In Minecraft Bedrock 1.20.80, Mojang deprecated and removed those JSON event responses for any block using a "format_version" of 1.20.80 or higher. Instead of writing complex Molang and event chains inside your block JSON, you now attach a single component—"minecraft:custom_components"—to your block, and handle the actual logic in TypeScript or JavaScript using the "@minecraft/server" Script API.

This shift makes blocks cleaner to define and vastly more capable. Rather than being limited to a handful of JSON event responses like "set_block_state" or "run_command", your block events have access to the full scripting environment.

Attaching Custom Components in Block JSON

To make a block listen for events, you declare the "minecraft:custom_components" component inside your block definition. It takes an array of custom component identifiers—strings formatted with a namespace and a name, just like your block identifier.

If you also want your block to respond to scheduled or random ticks, you still pair this with the standard "minecraft:queued_ticking" or "minecraft:random_ticking" components in the JSON, though you omit the old event trigger fields inside them. Below is a complete block definition for a toggleable lamp that uses a custom component.

BP/blocks/toggle_lamp.json
{
  "format_version": "1.21.0",
  "minecraft:block": {
    "description": {
      "identifier": "wiki:toggle_lamp",
      "states": {
        "wiki:lit": [false, true]
      }
    },
    "components": {
      "minecraft:destructible_by_mining": {
        "seconds_to_destroy": 1.5
      },
      "minecraft:custom_components": [
        "wiki:lamp_toggle_behavior"
      ]
    },
    "permutations": [
      {
        "condition": "q.block_state('wiki:lit') == true",
        "components": {
          "minecraft:light_emission": 15
        }
      },
      {
        "condition": "q.block_state('wiki:lit') == false",
        "components": {
          "minecraft:light_emission": 0
        }
      }
    ]
  }
}

Registering the Event Handler in Script

Declaring the identifier in JSON does nothing until you register a matching custom component in your Behavior Pack's script entry file. Registration must happen during world initialization, specifically inside the "worldInitialize" (or "startup" in newer API versions) before-event, using the "blockComponentRegistry" object.

You pass the exact string identifier you used in your JSON, followed by an object containing the event methods you want to listen to. For our lamp, we implement "onPlayerInteract" to flip the "wiki:lit" block state and play a click sound whenever a player uses the block.

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

world.beforeEvents.worldInitialize.subscribe((eventData) => {
  eventData.blockComponentRegistry.registerCustomComponent(
    "wiki:lamp_toggle_behavior",
    {
      onPlayerInteract(event) {
        const { block, dimension } = event;
        const currentState = block.permutation.getState("wiki:lit");
        const newPermutation = block.permutation.withState("wiki:lit", !currentState);
        
        block.setPermutation(newPermutation);
        dimension.playSound("random.click", block.location);
      }
    }
  );
});

Available Block Event Hooks

A single custom component object can implement multiple event hooks at once. Each hook receives an event argument containing context about what happened—always including the "block" and "dimension" where the event occurred, and often the "player" or "entity" involved.

Because a block can list multiple entries in its "minecraft:custom_components" array, you can build reusable behaviors. For example, you might have one component that handles crop growth on random ticks, and a separate component that handles fertilizing when a player interacts with bone meal.

Method NameWhen it triggersKey Event Properties
onPlaceWhen the block is placed in the world by a player or script.block, dimension, previousBlock
onPlayerInteractWhen a player right-clicks or taps the block.block, dimension, player, face, faceLocation
onPlayerDestroyAfter a player finishes mining and breaking the block.block, dimension, player, destroyedBlockPermutation
onStepOnWhen an entity steps onto the top surface of the block.block, dimension, entity
onStepOffWhen an entity walks or jumps off the top surface of the block.block, dimension, entity
onEntityFallOnWhen an entity falls and lands on the block (requires a minimum fall distance configured in JSON).block, dimension, entity, fallDistance
onTickWhen a scheduled tick fires (requires "minecraft:queued_ticking" on the block).block, dimension
onRandomTickWhen a random chunk tick hits this block (requires "minecraft:random_ticking" on the block).block, dimension

Using Ticking Events

Two of the event hooks—"onTick" and "onRandomTick"—will not fire simply by registering them in your script. They still rely on JSON components to tell the game engine how and when to schedule ticks for the block.

For "onRandomTick", you must include ""minecraft:random_ticking": {}" as an empty object in your block's "components" list. The game will then call your script's "onRandomTick" method at the normal random tick rate. For "onTick", you must include "minecraft:queued_ticking" in your JSON with a "looping" boolean and an "interval_range" array of two numbers (minimum and maximum ticks between executions).

BP/blocks/ticking_machine.json
{
  "format_version": "1.21.0",
  "minecraft:block": {
    "description": {
      "identifier": "wiki:ticking_machine"
    },
    "components": {
      "minecraft:queued_ticking": {
        "looping": true,
        "interval_range": [20, 20]
      },
      "minecraft:custom_components": [
        "wiki:machine_tick_logic"
      ]
    }
  }
}

Which versions this applies to

Applies to Minecraft Bedrock 1.21.0 and later (introduced as stable in 1.20.80). Does not require any experimental toggles when using stable "@minecraft/server" API versions (1.11.0 or higher) in your manifest.json. Legacy JSON block events only function on format_version 1.20.70 or older with the Holiday Creator Features toggle enabled, which is now removed from current releases.

When it does not work

What you seeWhyThe fix
Content Log error: "Failed to find custom component 'wiki:lamp_toggle_behavior'" or the block does nothing when clicked.You either forgot to register the component in your script, misspelled the identifier between the JSON and JS files, or attempted to register it outside the world initialization event.Ensure "registerCustomComponent" is called inside "world.beforeEvents.worldInitialize.subscribe" (and before any "await" statements), and check that your Behavior Pack manifest.json has a valid "script" module entry and "@minecraft/server" dependency.
Content Log error: "minecraft:on_interact is not a valid component" when loading into a world.Your block JSON uses a modern "format_version" (such as 1.20.80 or 1.21.0) while trying to use removed Holiday Creator Features event trigger components.Remove "minecraft:on_interact", "minecraft:on_placed", and the "events" object from your block JSON. Replace them with "minecraft:custom_components" and move the response logic into your script file.
The "onTick" method in the custom component never runs, even though "onPlayerInteract" on the same component works fine.Registering "onTick" in JavaScript only sets up the listener; the block does not actually tick unless told to do so in its JSON definition.Add the "minecraft:queued_ticking" component to your block's JSON "components" (or a permutation) with a valid "interval_range", such as "[20, 20]".
Noticeable lag spikes when placing hundreds of custom blocks that use "onTick".Every block with "minecraft:queued_ticking" set to a short interval executes JavaScript code simultaneously. Running heavy logic or world queries every tick across many blocks quickly exhausts the script watchdog budget.Use "minecraft:random_ticking" with "onRandomTick" if exact timing is not required, increase the "interval_range" on "minecraft:queued_ticking", or only enable the ticking component inside an active permutation (for example, when a machine block is switched on).

Reference, straight from the game files

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