How Tick-Driven Scoreboard Timers Work
Minecraft Bedrock runs its simulation loop at a target rate of 20 game ticks per second (1 tick every 0.05 seconds). Because Bedrock's `scoreboard` command only offers the `dummy` criteria for objectives, it does not automatically increment scores over time. To build a timer, you create a `dummy` objective and manually add or subtract `1` from a target's score every single tick using a ticking function or a repeating command block.
Inside a behavior pack, the standard way to drive a timer is by registering a `.mcfunction` file inside `BP/functions/tick.json`. Every command inside a registered ticking function runs once per game tick at the world origin. By incrementing or decrementing a score inside that function and checking for a target threshold using `@e[scores={...}]` or `execute if score`, you turn raw game ticks into reliable, persistent clocks.
There are two distinct patterns for storing these values: entity-bound scores and fake player scores. An entity-bound score attaches the timer directly to a player or mob (`@a`, `@e[type=...]`). This is ideal for player ability cooldowns or custom boss attack phases, because every entity gets its own independent clock. A fake player score attaches the timer to a text string that does not match any real player (usually prefixed with a hash, like `#match_clock`). Fake players never despawn, do not require loaded chunks, and do not show up in the pause menu player list, making them the standard choice for global match timers or environmental cycles.
| Real Time | Game Ticks | Common Add-on Use Case |
|---|---|---|
| 0.25 seconds | 5 ticks | Fast UI updates, custom weapon fire rates, particle trails |
| 1 second | 20 ticks | Standard HUD countdown clocks, damage-over-time ticks |
| 5 seconds | 100 ticks | Short ability cooldowns, combat tag expiration |
| 1 minute | 1200 ticks | Wave survival intervals, temporary buff durations |
| 20 minutes | 24000 ticks | One full Minecraft daylight cycle, full minigame round |
Registering the Tick Loop and Objectives
Before a timer can count, the scoreboard objective must exist in the world. If a ticking function tries to add to an objective that has not been created yet, the command silently fails every tick. While you can run `/scoreboard objectives add` once in chat, a self-contained behavior pack should initialize its own objectives inside the ticking function or via an initialization function.
To run your timer logic automatically every tick, create `BP/functions/tick.json` and list the path to your main loop function relative to the `BP/functions/` folder (omitting the `.mcfunction` extension). When a world loads with your behavior pack applied, Bedrock reads `tick.json` and executes the listed function files 20 times per second.
{
"values": [
"timers/main_loop"
]
}Pattern 1: Per-Entity Cooldowns (Count-Down)
For player cooldowns, status effects, or mob despawn delays, counting down to zero is far easier to manage than counting up. When an ability triggers, you set that specific entity's score to the desired tick duration (for example, `100` for 5 seconds). Every tick, you filter for entities whose score is `1` or greater and subtract `1`.
This pattern has two major advantages. First, you can change the duration of a cooldown dynamically by simply setting a different starting number when the event begins, without touching the ticking function. Second, the selector `@a[scores={ability_cd=1..}]` automatically stops matching the player the moment their score hits `0`, so the game stops running commands on idle players.
Always check for the completion trigger (usually at score `1`, right before it becomes `0`, or at `0` using a state tag) in the correct order within your function. In a `.mcfunction` file, commands execute sequentially from top to bottom within the same game tick. If you set a score to `100` and decrement it in the same tick, or trigger the 'ready' notification before decrementing, your timing will be off by one tick or fire prematurely.
# Ensure the objective exists (safe to run; fails silently once created)
scoreboard objectives add ability_cd dummy
# Notify the player on the exact tick their cooldown finishes ( transitioning from 1 to 0 )
execute as @a[scores={ability_cd=1}] run titleraw @s actionbar {"rawtext":[{"text":"Ability Ready!"}]}
execute as @a[scores={ability_cd=1}] at @s run playsound random.orb @s ~~~ 0.8 1.2
# Decrement the timer for anyone currently on cooldown
scoreboard players remove @a[scores={ability_cd=1..}] ability_cd 1Pattern 2: Global Match Clock (Minutes and Seconds)
A raw tick counter climbs to `1200` in a single minute, which looks chaotic if you display it directly on the sidebar or actionbar. To show a readable `Minutes : Seconds` clock to players, you chain three fake player scores together inside a single objective (or separate objectives): one for ticks, one for seconds, and one for minutes.
In this setup, `#ticks` increments by `1` every tick. Once `#ticks` reaches `20`, one full second has passed: you reset `#ticks` to `0` and remove `1` from `#seconds`. If `#seconds` drops below `0` while `#minutes` is still `1` or greater, you subtract `1` from `#minutes` and wrap `#seconds` back to `59`.
Prefixing fake player names with `#` (such as `#ticks`) has a built-in benefit in Bedrock: names starting with `#` are hidden from the `sidebar` and `list` display slots, even if the objective itself is currently set to display on the screen. You only expose the scores you want players to see, or you combine `#minutes` and `#seconds` inside a `titleraw` actionbar command using the `score` rawtext component.
# Run this function only while the match is active
scoreboard players add #ticks match_time 1
# Every 20 ticks (1 second), reset ticks and decrement seconds
execute if score #ticks match_time matches 20.. run scoreboard players remove #seconds match_time 1
execute if score #ticks match_time matches 20.. run scoreboard players set #ticks match_time 0
# Roll over minutes when seconds drop below 0 and minutes remain
execute if score #seconds match_time matches ..-1 if score #minutes match_time matches 1.. run scoreboard players remove #minutes match_time 1
execute if score #seconds match_time matches ..-1 if score #minutes match_time matches 0.. run scoreboard players set #seconds match_time 59
# Display formatted M:SS clock on all players' actionbars
titleraw @a actionbar {"rawtext":[{"text":"Time Remaining: "},{"score":{"name":"#minutes","objective":"match_time"}},{"text":":"},{"score":{"name":"#seconds","objective":"match_time"}}]}
# Check for match end (0 minutes and 0 seconds remaining)
execute if score #minutes match_time matches 0 if score #seconds match_time matches ..0 run function timers/end_matchDriving Timers From Entity Events and Animation Controllers
While `tick.json` is the cleanest place for global clocks and player cooldowns, you sometimes need a timer tightly coupled to a custom entity's state—for example, a custom bomb entity that ticks down only while it is primed, or a boss that cycles attacks only when a target is nearby. Running `@e[type=...]` selectors in `tick.json` for dozens of custom entity types adds unnecessary selector overhead across the whole world.
Instead, you can drive the scoreboard increment inside a Behavior Pack Animation Controller (`BP/animation_controllers/`). Behavior pack animation controllers evaluate their state transitions every tick on the entity they are attached to, and they can execute slash commands directly as `@s`. However, if you only need a simple fixed-duration delay on an entity before firing an component group change, consider using the `minecraft:timer` entity component in the entity's behavior JSON first. Reach for a scoreboard timer in an animation controller only when you need to read, pause, modify, or display the exact tick count via commands.
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.boss_phase_timer": {
"initial_state": "idle",
"states": {
"idle": {
"transitions": [
{
"combat_loop": "query.has_target"
}
]
},
"combat_loop": {
"on_entry": [
"/scoreboard players add @s boss_attack_ticks 1",
"/execute if score @s boss_attack_ticks matches 100.. run event entity @s custom:trigger_special_attack",
"/execute if score @s boss_attack_ticks matches 100.. run scoreboard players set @s boss_attack_ticks 0"
],
"transitions": [
{
"idle": "!query.has_target"
},
{
"re_enter": "1.0"
}
]
},
"re_enter": {
"transitions": [
{
"combat_loop": "1.0"
}
]
}
}
}
}
}Dealing with Unloaded Chunks and Offline Players
Target selectors like `@e` and `@a` only find entities that are currently loaded in the world. If you attach a timer score to an armor stand or a custom marker entity and all players walk far enough away for that chunk to unload, `@e[type=my_addon:marker]` will find nothing. The timer freezes until the chunk is loaded again (or until you cover the area with a `/tickingarea`).
Fake players (`#global_timer`) do not exist in physical world space. Commands targeting a fake player by name (`scoreboard players add #global_timer ...` and `execute if score #global_timer ...`) work regardless of which chunks are loaded. Whenever a timer represents a world-wide or match-wide state, store it on a fake player rather than an entity.
For real players (`@a`), scores persist in the world's scoreboard database when the player disconnects, but `@a` will not select them while they are offline. Consequently, an offline player's cooldown timer pauses until they rejoin the server. If a player disconnects during a timed match buff and rejoins after the match is over, their score will still hold whatever positive integer it had when they left. Always reset or validate player timer scores upon joining or when a round restarts.
| Score Holder Type | Works in Unloaded Chunks? | Counts While Offline? | Best Used For |
|---|---|---|---|
| Fake Player (`#name`) | Yes (no physical position) | Always active while world runs | Global match clocks, weather/event cycles |
| Real Player (`@a`) | Yes (players load own chunks) | No (pauses until rejoin) | Ability cooldowns, personal buffs, combat tags |
| Non-Player Entity (`@e`) | No (unless in a `tickingarea`) | Pauses when chunk unloads | Mob attack telegraphs, local machine timers |
Which versions this applies to
Applies to Bedrock Edition 1.19.50 and newer (uses the modern `execute if score ... matches` syntax introduced in the `execute` command overhaul). No experimental toggles are required for functions, `tick.json`, scoreboards, or behavior pack animation controllers. Ensure `manifest.json` sets `min_engine_version` to at least `[1, 19, 50]` so functions run with the modern `execute` syntax.
When it does not work
| What you see | Why | The fix |
|---|---|---|
| Timer actions fire every single tick instead of once when the timer finishes, or `execute if score` fails with a syntax error in the Content Log. | You either used an unbounded range (`matches 20..`) without resetting the score back to `0` on the same tick, or your behavior pack's `manifest.json` has a `min_engine_version` older than `1.19.50`, causing Bedrock to parse `.mcfunction` files using the legacy `execute` syntax. | Update `min_engine_version` in `BP/manifest.json` to `[1, 19, 50]` or higher, and always reset or decrement the score immediately after checking the threshold so the condition is only true for one tick. |
| Entities or players who have never had their timer started do not match `@a[scores={my_timer=0}]`. | In Bedrock, a newly spawned entity or newly joined player has `null` (no score at all) on an objective, which is not equal to `0`. A selector checking for `0` will ignore them. | Initialize the player's score to `0` using `scoreboard players add @a my_timer 0` (adding `0` initializes unset scores to `0` without changing existing positive scores), or trigger your cooldown by setting the score to a positive number and only testing for `1..`. |
| Content Log shows `Function timers/main_loop not found` or the timer never increments at all. | `BP/functions/tick.json` includes the `.mcfunction` file extension in the string, uses backslashes instead of forward slashes, or the objective was never created with `scoreboard objectives add`. | In `BP/functions/tick.json`, write paths relative to `BP/functions/` using forward slashes and no file extension (e.g., `"timers/main_loop"`), and verify the objective exists using `/scoreboard objectives list`. |
| Actionbar clock displays `Time Remaining: *:*` instead of numbers. | In `titleraw`, the `score` rawtext component renders an asterisk (`*`) when the specified objective does not exist or the fake player (`#minutes` / `#seconds`) has not been assigned an initial value yet. | Run `scoreboard players add #minutes match_time 0` and `scoreboard players add #seconds match_time 0` during setup so both fake players hold valid integer values before `titleraw` runs. |
Reference, straight from the game files
Read out of the game itself, so the ids match the version you are building for.
