Wiki 9Minecraft Wiki 9Minecraft

On Player Leave Command and Script Recipes

Detect when a player disconnects from a Bedrock world and run cleanup commands or scripts.

6Sections
5Code examples
4Known errors

Detecting when a player disconnects from a world is essential for multiplayer maps, minigames, and servers that need to clean up active matches, reset arenas, or log session times. Because a leaving player's entity is removed from the world before standard tick functions can inspect it, you cannot target them directly with `@a` or `@s` after they leave. This reference covers both the pure scoreboard command approach for identifying offline entries and the modern Script API event system for immediate, reliable leave handling.

Why Player Leave Detection is Tricky

In Minecraft Bedrock Edition, every command selector like `@a`, `@p`, or `@e[type=player]` only evaluates players who are currently connected and loaded into the world. The exact moment a player clicks Save and Quit or loses their connection, their entity ceases to exist in the active entity list. By the time your next repeating command block or `tick.json` function runs, there is no player entity left to run `/execute as` against.

Because of this lifecycle limitation, any detection system must rely on state that persists independently of the player entity itself, or hook directly into the engine's disconnection sequence. In a pure command and function setup, the only built-in structure that retains references to offline players is the scoreboard system. When an entity is tracked on a scoreboard objective, their score remains stored under their name or internal identifier until explicitly reset, even after they leave the server.

If your add-on uses the `@minecraft/server` Script API, the problem is much simpler. The engine fires specific world after-events and before-events during the disconnection process, handing your script the player's name and unique ID directly without needing to poll scoreboard states every tick. Which approach you choose depends on whether your project is strictly a data-driven behavior pack using `.mcfunction` files or a script-enabled pack.

MethodBest ForLimitations
Scoreboard Offline PollingNo-script behavior packs, simple lobby resets, single-slot minigamesCannot read the leaving player's name or inventory; triggers for all offline score holders unless reset immediately
Script API playerLeave After-EventLogging, team balancing, custom leave messages, saving world statePlayer entity is already gone; you only get playerId and playerName strings
Script API playerLeave Before-EventInspecting player components or dynamic properties right before disconnectRuns in read-only mode; requires system.run() to modify the world on the next tick

Pure Command Recipe: The Offline Scoreboard Pattern

To detect a disconnect using only commands, you take advantage of the wildcard scoreboard target `*`. In Bedrock scoreboard operations, `@a` refers exclusively to online players, whereas `*` refers to every tracked participant on the scoreboard—including players who have logged out, non-player entities, and fake player names.

The core loop works inside a ticking function by assigning every tracked participant a temporary state, overwriting that state for everyone who is currently online, and then reacting to any participant whose state was not overwritten. To set this up, create an objective named `online_check` once during your world initialization (for example, `/scoreboard objectives add online_check dummy`).

Every tick, your function first sets the `online_check` score of `*` (everyone ever tracked) to `0`. Immediately on the next line, it sets the `online_check` score of `@a` (everyone currently online) to `1`. If a player was online during the previous tick, they are tracked on the board. The moment they leave, the first command sets their score to `0`, and the second command skips them because they are no longer part of `@a`. You can then test if any participant has a score of `0` and trigger your cleanup logic, immediately followed by `scoreboard players reset * online_check` so the leave trigger only fires once per disconnection.

Be careful if you use fake players (like `#timer` or `global_state`) on other objectives in your world. Because `*` targets every participant across the entire scoreboard database, a fake player used for a timer on a completely different objective will also be given an `online_check` score of `0` by `scoreboard players set * online_check 0`, causing a false leave detection. If you use this command recipe, avoid fake player names on your scoreboards and attach utility scores to a persistent marker entity instead.

BP/functions/check_player_leave.mcfunction
# Run this function every tick via BP/functions/tick.json
# Prerequisite: /scoreboard objectives add online_check dummy

# Step 1: Mark all known scoreboard participants as offline (0)
scoreboard players set * online_check 0

# Step 2: Mark all currently connected players as online (1)
scoreboard players set @a online_check 1

# Step 3: If any participant still has a score of 0, someone just left.
# Run your world cleanup or arena reset commands here:
execute if score * online_check matches 0..0 run say A tracked player has left the world!
execute if score * online_check matches 0..0 run tag @a[tag=in_duel] remove in_duel

# Step 4: Wipe the offline entry from the board so it does not trigger again next tick,
# then re-register currently online players for the next tick's check.
execute if score * online_check matches 0..0 run scoreboard players reset * online_check
scoreboard players set @a online_check 1

Registering the Tick Function

For the scoreboard polling recipe to work reliably, it must run every single game tick without fail. Instead of placing repeating command blocks in a ticking area in the world, an add-on should register the function inside `BP/functions/tick.json`.

The `tick.json` file takes a simple array of function paths relative to the `BP/functions/` folder (omitting the `.mcfunction` extension). Keep in mind that you still need to create the `online_check` dummy objective at least once in the world. You can safely run `scoreboard objectives add online_check dummy` inside a setup function, or even at the top of your tick function if you do not mind it silently failing inside the background log once the objective already exists.

BP/functions/tick.json
{
  "values": [
    "check_player_leave"
  ]
}

Tracking Specific Roles or Teams on Leave

The basic wildcard recipe tells you that *someone* left, which is enough for a 1v1 arena where either player leaving should end the match. However, in a larger multiplayer game, you usually only care if a specific player leaves—such as the player currently acting as the seeker in a hide-and-seek minigame, or a player inside a boss room.

Because you cannot check tags (`@a[tag=seeker]`) on a disconnected player, you must mirror that role onto its own dedicated scoreboard objective while the player is still online. Instead of tracking all players with `@a`, only add players with the relevant tag to the tracking objective.

For example, if you create an objective called `seeker_online`, you only set `scoreboard players set @a[tag=seeker] seeker_online 1` every tick. When you run `scoreboard players set * seeker_online 0` right before it, only players who previously held a score on `seeker_online` (or other tracked participants if you are not resetting properly) are affected. To avoid polluting the objective with non-seeker participants, you can use `scoreboard players add @a[tag=seeker] seeker_online 0` to register them, and instead of `set *`, use `scoreboard players remove * seeker_online 1` on the objective, or reset everyone who is not a seeker while they are still online (`scoreboard players reset @a[tag=!seeker] seeker_online`).

BP/functions/check_seeker_leave.mcfunction
# Prerequisite: /scoreboard objectives add seeker_online dummy

# Stop tracking players who are online but no longer have the seeker tag
scoreboard players reset @a[tag=!seeker] seeker_online

# Subtract 1 from everyone currently tracked on theboard (online seekers drop from 1 to 0)
scoreboard players remove * seeker_online 1

# Restore online seekers back to 1
scoreboard players set @a[tag=seeker] seeker_online 1

# Anyone who was a seeker and disconnected could not be restored to 1, so their score is now 0.
execute if score * seeker_online matches ..0 run tellraw @a {"rawtext":[{"text":"The seeker disconnected! Selecting a new seeker..."}]}
execute if score * seeker_online matches ..0 run tag @r add seeker

# Clean up the disconnected entry and re-mark the new seeker
execute if score * seeker_online matches ..0 run scoreboard players reset * seeker_online
scoreboard players set @a[tag=seeker] seeker_online 1

Script API Recipe: Exact Leave Detection

When your add-on requires the leaving player's exact username, unique identifier, or a clean way to handle multiple players disconnecting on the exact same tick without scoreboard collisions, you should use the `@minecraft/server` Script API. The Script API is stable and does not require the Beta APIs experimental toggle when using standard stable module versions.

The `world.afterEvents.playerLeave` event fires immediately after a player has disconnected from the world. The event callback receives a `PlayerLeaveAfterEvent` object containing two string properties: `playerId` and `playerName`. Because this is an `afterEvent`, the player's `Player` entity object has already been destroyed and removed from the world, so you cannot call entity methods or inspect their inventory directly inside this callback.

If you need to run commands or modify entities when a player leaves, you can do so directly inside the `afterEvents.playerLeave` callback using `world.getDimension("overworld").runCommand()` or native Script API methods. If you also need to know what team or state the player had before they left, store that data in a JavaScript `Map` keyed by `player.id` while they are playing, or read it inside `world.beforeEvents.playerLeave` (which still provides a read-only reference to the `player` entity before it is removed) and pass the data into a `system.run()` block to execute changes on the next tick.

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

// Optional: Read data from the player entity right before it is destroyed
world.beforeEvents.playerLeave.subscribe((event) => {
  const leavingPlayer = event.player;
  const wasInArena = leavingPlayer.hasTag("in_arena");

  if (wasInArena) {
    // beforeEvents run in read-only mode; defer world modifications with system.run
    system.run(() => {
      const overworld = world.getDimension("overworld");
      overworld.runCommand("scoreboard players set @a[tag=in_arena] arena_timer 0");
      world.sendMessage(`${leavingPlayer.name} forfeited the arena match by disconnecting.`);
    });
  }
});

// Standard after-event: fires once the player has fully disconnected
world.afterEvents.playerLeave.subscribe((event) => {
  const { playerName, playerId } = event;

  // Broadcast a custom leave message or clean up script memory
  world.sendMessage(`[Server] ${playerName} (ID: ${playerId}) has left the session.`);
});

Behavior Pack Manifest for Scripted Leave Detection

To run the JavaScript recipe above, your behavior pack's `manifest.json` must declare a `script` module alongside the standard `data` module, and specify `@minecraft/server` in the `dependencies` array. Make sure the `entry` path matches the actual location of your script file inside the behavior pack.

Using a stable version string such as `1.14.0` or `1.15.0` for `@minecraft/server` allows your pack to work on standard worlds and Realms without turning on any experimental toggles in the world settings.

BP/manifest.json
{
  "format_version": 2,
  "header": {
    "name": "Player Leave Handler BP",
    "description": "Handles player disconnections via commands and Script API.",
    "uuid": "a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d",
    "version": [1, 0, 0],
    "min_engine_version": [1, 21, 0]
  },
  "modules": [
    {
      "type": "data",
      "uuid": "b2c3d4e5-f6a7-4b6c-9d0e-1f2a3b4c5d6e",
      "version": [1, 0, 0]
    },
    {
      "type": "script",
      "language": "javascript",
      "uuid": "c3d4e5f6-a7b8-4c7d-8e1f-2a3b4c5d6e7f",
      "entry": "scripts/main.js",
      "version": [1, 0, 0]
    }
  ],
  "dependencies": [
    {
      "module_name": "@minecraft/server",
      "version": "1.15.0"
    }
  ]
}

Which versions this applies to

The scoreboard wildcard command recipe applies to all modern Bedrock versions (1.19.50+ with current execute syntax) and requires no experimental toggles. The Script API playerLeave events are stable in @minecraft/server 1.4.0+ (Minecraft Bedrock 1.20.10 and newer) with no experimental toggles required when using a stable module version.

When it does not work

What you seeWhyThe fix
The scoreboard leave command triggers every single tick even when nobody has left the game.You have a non-player entity (like an armor stand) or a fake player name tracked on another scoreboard objective in the world. Because `scoreboard players set * online_check 0` sets a score of 0 for every participant in the global scoreboard database, and `set @a online_check 1` only updates real online players, the fake player or armor stand remains at 0 forever.Use the `scoreboard players remove *` pattern so only participants already registered on that specific objective are modified, or avoid fake players and include non-player score holders in your reset step (for example, `scoreboard players reset @e[type=!player] online_check`).
Content Log Error: [Scripting][error]-ReferenceError: Cannot modify the world in beforeEvents (or NativeCallFailed).You tried to call `runCommand`, `addTag`, `setDynamicProperty`, or spawn an entity directly inside `world.beforeEvents.playerLeave.subscribe()`. All `beforeEvents` in `@minecraft/server` execute in a restricted read-only phase of the tick.Wrap your world-modifying code or `runCommand` calls inside `system.run(() => { ... })` so they execute at the start of the next writable tick, or move the logic to `world.afterEvents.playerLeave` if you do not need to read components from the player entity.
Content Log Error: [Commands][warning]-Function check_player_leave failed to load correctly / Unknown objective 'online_check'.Your `tick.json` started running `check_player_leave.mcfunction` before the `online_check` scoreboard objective was created in the world.Run `/scoreboard objectives add online_check dummy` once in the world, or add `scoreboard objectives add online_check dummy` at the top of a setup function triggered when the first player spawns.
TypeError: Cannot read properties of undefined (reading 'name') inside world.afterEvents.playerLeave.You tried to access `event.player.name` inside `afterEvents.playerLeave`. Unlike `beforeEvents.playerLeave`, the after-event does not have a `player` property because the entity has already despawned.Access `event.playerName` and `event.playerId` directly on the event object in `afterEvents.playerLeave`.

Reference, straight from the game files

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