Worlds and blocks
Reach the level and its dimensions, read blocks at coordinates, and place blocks with custom block states.
A Bedrock world is a level, and a level holds one or more dimensions - the overworld, the nether, and the end. Blocks live inside a dimension, addressed by their x, y, z coordinates. To touch a block you first reach its dimension, then ask the dimension for the block at a position.
Reach the level and its dimensions
The server exposes the running world at self.server.level. From the level you read its name and seed, and you enumerate the dimensions inside it:
from endstone.level import Dimension
from endstone.plugin import Plugin
class MyPlugin(Plugin):
api_version = "0.11"
def on_enable(self) -> None:
level = self.server.level
self.logger.info(f"World: {level.name}, seed {level.seed}")
for dimension in level.dimensions:
self.logger.info(f" dimension {dimension.id}")
overworld = level.get_dimension(Dimension.OVERWORLD)Levelis the world as a whole - itsname,seed, the in-gametime, and the list ofactorsacross every dimension.Dimensionis one space within that level. Get them all fromlevel.dimensions, or fetch one by id withlevel.get_dimension(Dimension.OVERWORLD). The constantsDimension.OVERWORLD,Dimension.NETHER, andDimension.THE_ENDareIdentifiers, andget_dimensionalso accepts the raw string ("minecraft:overworld"). Each dimension'sidis that sameIdentifier.
The in-game time lives on the level, not the dimension: read and write level.time as an integer of ticks. Endstone's level API does not currently expose weather or game rules - drive those with the vanilla /weather and /gamerule commands through server.dispatch_command if you need them.
Locations and positions
A Location is a point inside a dimension: a dimension plus x, y, z, and an optional pitch and yaw facing. It's what blocks report for their position and what teleports and spawns consume.
from endstone.level import Location
loc = Location(overworld, 100.0, 64.0, -200.0)
loc.x, loc.y, loc.z # the float coordinates
loc.block_x, loc.block_y, loc.block_z # floored to the block they sit in
loc.dimension # the dimension this location belongs toThe coordinates are floats, so a location can sit anywhere within a block. The block_x / block_y / block_z properties floor each axis to the integer coordinate of the block that contains the point - exactly what you pass when looking a block up by coordinate.
Read a block
Ask a dimension for the block at integer coordinates, or hand it a Location. Either way you get a live Block whose type and data reflect the world right now:
block = overworld.get_block_at(100, 64, -200)
# or, from a location:
block = overworld.get_block_at(loc)
block.type # "minecraft:stone" - the block's identifier
block.data # a BlockData snapshot, including its block states
block.location # where it sits, as a Location
block.get_relative(0, 1, 0) # the block one aboveblock.typeis the identifier string, like"minecraft:grass_block".block.datareturns aBlockDatadescribing the block fully. Itsblock_statesis a dict of the per-block properties - direction, waterlogging, growth stage, and so on - keyed by name withbool,int, orstrvalues.get_relativesteps to a neighbouring block, either by an offset or by aBlockFace(BlockFace.UP,DOWN,NORTH,SOUTH,EAST,WEST) and an optional distance.
data = block.data
self.logger.info(f"{data.type} states={data.block_states}")Change a block
Set a block's type by identifier, or set its full data to carry block states. Both write straight to the world:
block.set_type("minecraft:stone")To place a block with specific states - a stair facing a direction, a waterlogged fence, a particular wood - build a BlockData with self.server.create_block_data(type, states) and apply it. The states map is the same shape you read back from block_states:
data = self.server.create_block_data(
"minecraft:oak_stairs",
{"weirdo_direction": 2, "upside_down_bit": False},
)
block.set_data(data)create_block_data fills every property with its default, then overrides only the keys you pass, so you set just the states you care about. Both set_type and set_data take an optional apply_physics flag (default True); pass False to skip the neighbour updates when you're placing many blocks and will settle physics yourself.
Block-state names and values are vanilla Bedrock data - they vary per block. An unknown state key or an out-of-range value is silently dropped or clamped, so verify the names for the block you're placing (for example with /setblock and tab-completion in game, or by reading an existing block's block_states).
React to block changes
When you care about blocks players break or place rather than ones you set yourself, handle the block events. They live on the events page - BlockBreakEvent and BlockPlaceEvent both carry the block and the player responsible:
from endstone.event import event_handler, BlockBreakEvent
class MyPlugin(Plugin):
api_version = "0.11"
def on_enable(self) -> None:
self.register_events(self)
@event_handler
def on_block_break(self, event: BlockBreakEvent) -> None:
self.logger.info(f"{event.player.name} broke {event.block.type}")Putting it together
A command that places a configured block where the player is standing. It reaches the player's dimension, floors their location to a block, builds the block data once, and writes it:
from endstone.command import Command, CommandSender
from endstone.level import Location
from endstone.player import Player
from endstone.plugin import Plugin
class MyPlugin(Plugin):
api_version = "0.11"
commands = {
"platform": {
"description": "Place a glass block under your feet",
"usages": ["/platform"],
}
}
def on_command(self, sender: CommandSender, command: Command, args: list[str]) -> bool:
if not isinstance(sender, Player):
sender.send_error_message("Run this in game.")
return True
loc = sender.location
block = loc.dimension.get_block_at(loc.block_x, loc.block_y - 1, loc.block_z)
data = self.server.create_block_data("minecraft:stained_glass", {"color": "light_blue"})
block.set_data(data)
sender.send_message(f"Placed {block.type} at {block.x}, {block.y}, {block.z}")
return TrueSee commands for how the command is declared and dispatched, and events for reacting to blocks players change themselves.