How the Global Counter Pattern Works
Scoreboards in Bedrock Edition only store 32-bit signed integers, meaning you cannot copy an entity's internal UUID string into a score. To work around this limitation without using Script API, you maintain a single global counter on a fake player name and increment it every time an entity needs a new ID. That entity copies the current counter value into its own score, ensuring no two entities ever receive the same number.
The critical requirement of this pattern is that the increment and the assignment must happen one at a time, even if twenty entities spawn on the exact same tick. If you run a plain `/scoreboard players operation` across `@e` without restricting the target count, every unindexed entity will copy the same counter value before the counter has a chance to tick up.
By combining the `/execute as` subcommand with the `c=1` target selector argument, you force the game to process unindexed entities individually, or you let each entity drive its own assignment in sequence. In Bedrock, when `/execute as @e[...]` runs a `.mcfunction` file, the entire function executes from top to bottom for the first matched entity before starting over for the second matched entity. This execution order makes single-tick batch assignment completely reliable.
| Component | Identifier / Syntax | Role in the Pattern |
|---|---|---|
| Objective | id | The dummy scoreboard objective that holds both the global counter and individual entity IDs. |
| Fake Player | #next_id | Holds the master counter. Prefixed with # so it never collides with a real Xbox Live gamertag. |
| Unassigned Filter | scores={id=!1..} | Matches any entity that does not yet have a positive ID score (including entities with no score at all). |
| Tag Guard | tag=!has_id | A secondary fast filter to prevent re-evaluating scoreboard ranges on every entity every tick. |
Setting Up the Scoreboard Objective
Before any IDs can be handed out, your behavior pack must create the scoreboard objective and initialize the fake player counter. Using a fake player name that begins with a hash symbol, such as `#next_id` or `.global_id`, guarantees that a real player with that name can never join the world and corrupt your counter. Furthermore, names starting with `#` are hidden from the sidebar display if you temporarily set the objective to `sidebar` while debugging.
Place the initialization commands inside a setup function that runs on world load, or wire it into `BP/functions/tick.json` alongside your main loop. Running `scoreboard objectives add` repeatedly is safe because Bedrock simply ignores the command if the objective already exists, though keeping it in a one-time setup function keeps your content log clean.
# Create the dummy objective for storing unique IDs
scoreboard objectives add id dummy "Unique ID"
# Ensure the global counter exists and starts at 0 if not already set
scoreboard players add #next_id id 0Assigning Unique IDs to Players Automatically
To automatically assign a unique ID to every player the moment they join the world, you need two small functions: a router function registered in `tick.json` and a worker function that performs the math. The router checks for any player who lacks the `has_id` tag and runs the worker function `as` that player.
Inside the worker function, `@s` refers to the single player currently being processed. The function first adds `1` to `#next_id`, then uses the `=` operator in `scoreboard players operation` to copy the value of `#next_id` onto `@s`. Finally, it applies the `has_id` tag so the player is skipped on all future ticks.
Because Bedrock executes the entire called function for the first player matched by `@a[tag=!has_id]` before moving to the next player in the selector list, even ten players joining simultaneously on a fresh server will receive clean, sequential numbers from `1` to `10` within the same game tick.
# Increment the master counter by 1
scoreboard players add #next_id id 1
# Copy the new counter value to the current entity or player
scoreboard players operation @s id = #next_id id
# Mark this target so it never receives a second ID
tag @s add has_idRegistering the Tick Loop
To make the assignment automatic, create a main tick function that invokes `id/assign_single` for any unindexed player or specific custom entity, and register that main function in `BP/functions/tick.json`. Avoid running the assignment across `@e[tag=!has_id]` without a `type` or `family` filter; otherwise, every dropped item, arrow, and experience orb in the world will consume an ID and bloat the world's scoreboard database.
Instead, target players directly with `@a[tag=!has_id]` and target only the custom entities that actually need linking by using a dedicated request tag such as `tag=needs_id`. When your custom entity spawns, you can grant it the `needs_id` tag through a spawn event in its behavior file, and the tick function will immediately assign its score and remove the request tag.
{
"values": [
"id/tick"
]
}Tick Router and Entity Linking Recipe
Once both a player and a secondary entity have scoreboard capability, you can link them together by copying the player's unique ID into a separate objective on the child entity, such as `owner_id`. When you want the child entity to teleport to its owner or apply an effect to them, you compare their scores using `scoreboard players operation`.
Because target selectors cannot evaluate a dynamic variable inside `scores={id=...}`, the standard Bedrock recipe for finding a linked partner is the subtraction test. You copy the child entity's `owner_id` to a temporary score on every candidate player, subtract each player's own `id` from that temporary score, and select the one player whose result equals `0`.
The function below shows both the tick router that triggers `id/assign_single` and the subtraction pattern used to locate the matching owner during gameplay.
# Ensure the objective exists
scoreboard objectives add id dummy
scoreboard objectives add temp_match dummy
# Assign unique IDs to any new players
execute as @a[tag=!has_id] run function id/assign_single
# Assign unique IDs to custom entities that requested one
execute as @e[tag=needs_id,tag=!has_id] run function id/assign_single
tag @e[tag=needs_id,tag=has_id] remove needs_id
# Example link check: executed AS a pet or marker that has an 'id' score matching its owner's 'id'
# Copy this entity's ID to all players on the temp_match objective
# execute as @e[type=minecraft:armor_stand,tag=linked_marker] run scoreboard players operation @a temp_match = @s id
# Subtract each player's own ID from their temp_match score
# execute as @e[type=minecraft:armor_stand,tag=linked_marker] as @a run scoreboard players operation @s temp_match -= @s id
# Whoever has temp_match == 0 is the owner of this marker
# execute as @e[type=minecraft:armor_stand,tag=linked_marker] at @a[scores={temp_match=0}] run tp @s ~ ~2 ~Triggering ID Assignment from an Entity File
If you are creating a custom entity in your behavior pack and want it to receive a unique ID the instant it enters the world, you do not even need to wait for the global tick loop. You can invoke `id/assign_single` directly inside the `minecraft:entity_spawned` event using a `queue_command` response.
When `queue_command` runs on an entity event, the execution context `@s` is already set to the newly spawned entity. It queues the function to run on the upcoming tick with `@s` bound to that exact instance, guaranteeing it gets a unique score before your gameplay systems interact with it.
{
"format_version": "1.20.40",
"minecraft:entity": {
"description": {
"identifier": "example:custom_drone",
"is_spawnable": true,
"is_summonable": true
},
"components": {
"minecraft:physics": {},
"minecraft:health": {
"value": 20,
"max": 20
}
},
"events": {
"minecraft:entity_spawned": {
"queue_command": {
"command": [
"function id/assign_single"
]
}
}
}
}
}Which versions this applies to
Applies to Minecraft Bedrock 1.19.50 and newer (uses the modern /execute syntax). No experimental toggles are required.
When it does not work
| What you see | Why | The fix |
|---|---|---|
| Multiple entities receive the exact same ID number when spawned on the same tick. | You placed the `scoreboard players add #next_id id 1` and `scoreboard players operation` commands directly inside the same function using `@e[tag=!has_id]` instead of calling a sub-function via `execute as @e[tag=!has_id] run function ...`. Without the sub-function call, the first line runs once, and the second line copies that single number to every matching entity at once. | Move the increment, operation, and tag commands into a separate `.mcfunction` file and call it with `execute as @e[tag=!has_id] run function id/assign_single` so the sequence completes per entity. |
| Content Log error: "No targets matched selector" or "Objective 'id' does not exist" on first world load. | `tick.json` starts running your assignment loop before the `id` objective has been created in the world. | Place `scoreboard objectives add id dummy` at the top of your tick function or run your setup function before the assignment check. |
| A player's ID resets or changes when they die and respawn. | You used a death-clearing mechanic or manually removed tags on respawn, or you tested on a non-player entity that despawned and was replaced by a new instance. | Player scoreboard values and tags persist through death automatically in Bedrock. Ensure none of your respawn functions run `tag @s remove has_id` or `scoreboard players reset @s id`. |
| Command output says: "Failed to execute 'scoreboard' as [Entity]" when using `queue_command` in `minecraft:entity_spawned`. | The `format_version` of your entity file or the `min_engine_version` in `BP/manifest.json` is set below 1.19.50 while your function uses the new `/execute as` syntax, or the function path has a leading slash. | Remove any leading slash from the function call (use `function id/assign_single`, not `/function /id/assign_single`) and set `min_engine_version` in `BP/manifest.json` to at least `[1, 19, 50]`. |
Reference, straight from the game files
Read out of the game itself, so the ids match the version you are building for.
