Scoreboards

Track scores and show per-player sidebars, name tags, and list panels.

A scoreboard holds named objectives, and each objective holds a score per entry - a player, an entity, or any plain string you choose. Display an objective in a slot and the client draws it: a sidebar on the right, a number under every player's name tag, or a column in the pause-screen player list.

Display an objective

The server keeps one primary scoreboard at self.server.scoreboard. Add an objective to it, drop the objective into a slot, and set scores - here a "blocks mined" tally in the sidebar that climbs as players dig:

src/endstone_my_plugin/my_plugin.py
from endstone.event import event_handler, BlockBreakEvent
from endstone.plugin import Plugin
from endstone.scoreboard import Criteria, DisplaySlot, ObjectiveSortOrder

class MyPlugin(Plugin):
    api_version = "0.11"

    def on_enable(self) -> None:
        self.objective = self.server.scoreboard.add_objective(
            "mined", Criteria.DUMMY, "Blocks Mined"
        )
        self.objective.set_display(DisplaySlot.SIDE_BAR, ObjectiveSortOrder.DESCENDING)
        self.register_events(self)

    @event_handler
    def on_block_break(self, event: BlockBreakEvent) -> None:
        score = self.objective.get_score(event.player)
        score.value += 1

    def on_disable(self) -> None:
        self.objective.unregister()

The pieces:

  • Criteria.DUMMY means the server never touches the score - your plugin owns it. It's the criteria you want for anything you drive yourself.
  • get_score(entry) returns a Score whose value you read and write. The entry can be a Player, an Actor, or a str, so score.value += 1 reads the current number and stores the new one.
  • set_display puts the objective in a slot - SIDE_BAR, BELOW_NAME, or PLAYER_LIST - with an optional sort order. unregister() removes the objective entirely; do it in on_disable so a reload starts clean.

If you've used the in-game /scoreboard command, the API maps onto it one-to-one - each call above is the programmatic form of a command you could type:

Python API/scoreboard command
add_objective("mined", Criteria.DUMMY, "Blocks Mined")/scoreboard objectives add mined dummy "Blocks Mined"
set_display(DisplaySlot.SIDE_BAR, ObjectiveSortOrder.DESCENDING)/scoreboard objectives setdisplay sidebar mined descending
get_score(player).value += 1/scoreboard players add @s mined 1
objective.unregister()/scoreboard objectives remove mined

Players, entities, and text rows

An entry is whatever a score hangs off. get_score accepts three kinds, and the type you pass decides what gets tracked:

objective.get_score(player).value = 10           # a real player, by identity
objective.get_score(zombie).value = 3            # any Actor (mob, entity)
objective.get_score("Server Total").value = 999  # plain text - a free-standing label row
  • A Player is tracked by the player's identity, so the score follows them across reconnects and renames, and shows under their name tag in the BELOW_NAME slot.
  • An Actor works the same way for entities - useful for per-mob counters.
  • A str is just text: a standalone named row tied to nobody. These are how you draw arbitrary text lines, like the "Coins" and "Level" rows in the next section.

Pass the Player object, not player.name. get_score(player) tracks the actual player; get_score(player.name) quietly creates a plain text row that merely shares the same characters - a separate entry that won't follow the real player, won't appear under their name tag, and drifts away from their real score the moment they rename. Only pass a string when you genuinely want a text row.

A different scoreboard for each player

This is where Endstone goes beyond what you can do from add-ons. The vanilla script API scoreboard and the /scoreboard command share one world scoreboard - every player necessarily sees the same objective and the same values. Endstone instead lets you build a scoreboard per player and assign it individually:

board = self.server.create_scoreboard()          # a fresh, independent scoreboard
objective = board.add_objective("hud", Criteria.DUMMY, "Your Stats")
objective.set_display(DisplaySlot.SIDE_BAR)

objective.get_score("Coins").value = coins        # plain strings make label rows
objective.get_score("Level").value = level

player.scoreboard = board                         # only this player sees it

create_scoreboard() hands back a standalone scoreboard, and player.scoreboard = board swaps which one that single player renders - so two players can be looking at completely different sidebars at the same moment. Because score entries can be arbitrary strings, each row is really a line of text you control, and the number on the right is just another field to drive.

Per-player scoreboards are the backbone of advanced JSON UI work. The sidebar is one of the few server-driven surfaces a resource pack can restyle, so pairing per-player objectives (the live data) with custom JSON UI (the look) is how server-side plugins build rich, personalised HUDs - health bars, quest trackers, shop menus - without a client mod. Drive the values from Python; let the resource pack render them. See the Bedrock Wiki's Intro to JSON UI for how the resource-pack side works.

To send everyone back to the shared view, assign the primary scoreboard again:

player.scoreboard = self.server.scoreboard

On this page