Scheduled Ticks vs. Random Ticks
Bedrock Edition provides two distinct ways to tick a block, and choosing the wrong one can severely hurt world performance. Scheduled ticks use the `minecraft:tick` component inside your block JSON file. They fire reliably within a specific tick range you define, regardless of the world's `randomTickSpeed` game rule. You should reach for scheduled ticks when a block needs predictable timing, such as a custom furnace, a redstone-like delay block, or a temporary bridge that disappears five seconds after being placed.
Random ticks do not require a dedicated timing component in your block JSON. Instead, they hook directly into the chunk's natural random tick cycle—the same system vanilla Minecraft uses to grow wheat and spread grass. When you attach a custom component that listens for the `onRandomTick` event in your script, the game automatically marks that block as eligible for random ticking. Because random ticks are chosen unpredictably per sub-chunk, they are much cheaper on performance when thousands of your custom blocks exist in the world at once.
Configuring the Scheduled Tick Component
To set up a scheduled tick, add `minecraft:tick` to the `components` object of your block's behavior pack JSON file, or inside a permutation if the block should only tick while in a certain state. Alongside `minecraft:tick`, you must attach `minecraft:custom_components` so the block knows which script handler to run when the timer expires.
In older versions of Bedrock under the retired Holiday Creator Features toggle, `minecraft:tick` pointed to a JSON block event using a `trigger` field. That system was removed from the engine. Today, `minecraft:tick` only controls the timing rules, while the actual behavior lives entirely in your TypeScript or JavaScript files.
| Field | What it does | Notes |
|---|---|---|
| interval_range | A two-number array `[min, max]` defining the delay in game ticks before the block ticks. | 20 game ticks equal 1 real-world second at normal game speed. Set both numbers to the same value for a fixed interval. |
| looping | A boolean that determines if the timer restarts after firing. | Defaults to `true`. If set to `false`, the block ticks once after being placed (or entering the permutation) and then stops. |
{
"format_version": "1.21.10",
"minecraft:block": {
"description": {
"identifier": "wiki:decaying_ice"
},
"components": {
"minecraft:destructible_by_mining": {
"seconds_to_destroy": 0.5
},
"minecraft:tick": {
"interval_range": [40, 80],
"looping": true
},
"minecraft:custom_components": [
"wiki:melt_over_time"
]
}
}
}Handling Ticks with Custom Components
Once your block JSON lists a custom component identifier, you must register that component during the world initialization phase in your behavior pack script. In `@minecraft/server`, the `BlockCustomComponent` interface provides two ticking methods: `onTick` for scheduled ticks driven by `minecraft:tick`, and `onRandomTick` for natural chunk random ticks.
Both methods receive a `BlockComponentTickEvent` (or `BlockComponentRandomTickEvent`), which gives you direct access to the `block` instance and the `dimension` it resides in. From there, you can inspect neighbors, change block permutations, spawn particles, or replace the block entirely.
import { world } from "@minecraft/server";
world.beforeEvents.worldInitialize.subscribe((event) => {
event.blockComponentRegistry.registerCustomComponent("wiki:melt_over_time", {
onTick(tickEvent) {
const { block } = tickEvent;
block.setType("minecraft:flowing_water");
}
});
});Building a Multi-Stage Crop with Random Ticks
A classic use case for `onRandomTick` is a crop block that advances through growth stages. Because a player might plant hundreds of crops in a single farm, using `minecraft:tick` on every plant would clog the tick queue. Instead, you omit `minecraft:tick` from the JSON and define a custom component that implements `onRandomTick`.
Below is a complete two-part setup. The block JSON defines a numeric state `wiki:growth` ranging from 0 to 3. The script listens for a random tick, checks the current state value, and increments it if the crop is not yet fully grown.
{
"format_version": "1.21.10",
"minecraft:block": {
"description": {
"identifier": "wiki:tomato_crop",
"states": {
"wiki:growth": [0, 1, 2, 3]
}
},
"components": {
"minecraft:geometry": "minecraft.geometry.crop",
"minecraft:custom_components": [
"wiki:crop_growth"
]
},
"permutations": [
{
"condition": "q.block_state('wiki:growth') == 3",
"components": {
"minecraft:custom_components": []
}
}
]
}
}Stopping Ticks via Permutations
Notice the permutation in `BP/blocks/tomato_crop.json` above: when `wiki:growth` reaches 3, `minecraft:custom_components` is overridden with an empty array `[]`. This is an important optimization pattern in Bedrock add-ons. Once a crop is fully mature, it no longer needs to run script logic on random ticks.
The same technique works for scheduled ticks. If you place `minecraft:tick` inside a specific permutation rather than the base `components` list, the block will only schedule ticks while that permutation's Molang condition is true. For example, a machine block might only have `minecraft:tick` in the permutation where `q.block_state('wiki:active') == true`, saving CPU time whenever the machine is turned off.
import { world } from "@minecraft/server";
world.beforeEvents.worldInitialize.subscribe((event) => {
event.blockComponentRegistry.registerCustomComponent("wiki:crop_growth", {
onRandomTick(tickEvent) {
const { block } = tickEvent;
const currentGrowth = block.permutation.getState("wiki:growth");
if (typeof currentGrowth === "number" && currentGrowth < 3) {
block.setPermutation(
block.permutation.withState("wiki:growth", currentGrowth + 1)
);
}
}
});
});Simulation Distance and Chunk Loading
Neither scheduled ticks nor random ticks will fire in chunks that are outside a player's simulation distance or a ticking area. When a chunk unloads while a block with `minecraft:tick` is waiting for its interval to finish, the remaining tick timer is saved with the chunk data and resumes when a player returns.
Because `minecraft:tick` does not track real-world time passed while a chunk is unloaded, a block with a five-minute interval will only count down those five minutes while the chunk is actively simulated. If you need a block to appear as though it progressed while the player was far away, you must design your script around `world.getAbsoluteTime()` when the chunk is interacted with, rather than relying solely on continuous block ticks.
Which versions this applies to
Applies to Bedrock Edition 1.20.80 and newer (format_version 1.20.80+ and 1.21.x). Both `minecraft:tick` and `minecraft:custom_components` are stable features and do not require any experimental toggles in your world settings. Note that in Bedrock 1.21.50+, registering custom components inside `world.beforeEvents.worldInitialize` was superseded by `system.beforeEvents.startup`, though the component methods (`onTick` and `onRandomTick`) remain identical.
When it does not work
| What you see | Why | The fix |
|---|---|---|
| Content Log shows: [Blocks][error]-block_name | minecraft:tick | Failed to parse 'trigger' or custom tick event does not fire at all. | You copied an outdated tutorial written for the removed Holiday Creator Features experiment, which used a `trigger` object inside `minecraft:tick` and an `events` block at the bottom of the JSON file. | Remove the `trigger` property from `minecraft:tick` and delete the JSON `events` section. Add `minecraft:custom_components` to the block and handle the logic using `onTick` in a behavior pack script. |
| The `onTick` callback in JavaScript never runs, even though the custom component is registered without errors and `onPlayerInteract` on the same component works fine. | Having `onTick` in your registered custom component is not enough by itself; the block does not know how often to tick unless `minecraft:tick` is also present in the block JSON. | Add the `minecraft:tick` component with a valid `interval_range` (such as `[20, 20]`) to the `components` or active permutation of your block JSON. |
| Content Log error: Cannot register custom component, or block loads into the world with no custom behavior. | Your script called `registerCustomComponent` after the world had already finished initializing (for example, inside `system.run` or after an async wait), or the identifier string in your script does not match the string in `minecraft:custom_components`. | Ensure `registerCustomComponent` is called synchronously inside the `worldInitialize` (or `startup` in 1.21.50+) before-event callback, and verify the namespace and name match the JSON character for character. |
| A block using `onRandomTick` takes several minutes to update once, making testing feel broken. | Default `randomTickSpeed` in Bedrock Edition is 1, which is intentionally slow (roughly one random block per sub-chunk every few ticks). | Run `/gamerule randomtickspeed 100` in chat while testing your `onRandomTick` logic, then reset it to `1` to tune your actual growth probabilities. |
Reference, straight from the game files
Read out of the game itself, so the ids match the version you are building for.
