Blocks
Read blocks at coordinates and place them with custom block states.
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.
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.