Block States, Permutations and Traits
This tutorial builds a Brass Lantern that can be lit, faces the direction it was placed in, only sits on stone, and ticks. It shows how a block changes with its states, how permutations swap components, how traits give the game-managed states, and how a custom component carries parameters to your scripts. Read Adding a Custom Block first.
The whole example compiles as written.
States and Traits
from anvil.api.blocks.blocks import Block
from anvil.lib.jsonmodel.enums import ItemCategory, ItemGroups, PlacementDirectionTrait
block = Block("brass_lantern")
description = block.server.description
description.set_menu_category(ItemCategory.Items, ItemGroups.Copper)
description.add_state("lit", (False, True))
description.traits.set_placement_direction(
traits=[PlacementDirectionTrait.CardinalDirection]
)
add_state(name, values)declares a state you control (up to 16 values). The name gets the project namespace:tut:lit.- A trait adds states the game sets on placement.
set_placement_directiongivesminecraft:cardinal_direction(orfacing_direction),set_placement_positiongivesblock_faceandvertical_half,set_connectionthe connection sides andset_multi_block(direction, part_count)the multi-block part.
"description": {
"identifier": "tut:brass_lantern",
"states": {"tut:lit": [false, true]},
"menu_category": {"category": "items", "group": "minecraft:itemGroup.name.copper"},
"traits": {"minecraft:placement_direction": {"enabled_states": ["minecraft:cardinal_direction"]}}
}
Root Components
from anvil.api.blocks.components import (
BlockCollisionBox,
BlockDestructibleByMining,
BlockGeometry,
BlockMaterialInstance,
BlockPlacementFilter,
BlockSelectionBox,
BlockTick,
InstanceSpec,
InstanceVariant,
)
from anvil.lib.jsonmodel.enums import BlockFaceValues
from anvil.api.vanilla.blocks import MinecraftBlockTypes
block.server.components.add(
BlockGeometry(blockbench="brass_lantern"),
BlockMaterialInstance().add_instance(
spec=InstanceSpec(
blockbench="brass_lantern",
variations=[InstanceVariant(color="brass_lantern")],
)
),
BlockCollisionBox(origin=(-4, 0, -4), size=(8, 10, 8)),
BlockSelectionBox(origin=(-4, 0, -4), size=(8, 10, 8)),
BlockDestructibleByMining(seconds_to_destroy=0.5),
BlockPlacementFilter().add_condition(
allowed_faces=[BlockFaceValues.Up],
block_filter=[MinecraftBlockTypes.Stone(), MinecraftBlockTypes.Cobblestone()],
),
BlockTick(interval_range=(40, 80), looping=True),
)
Note
BlockPlacementFilter limits where the block can be placed: here only on the top face of stone or cobblestone. BlockTick calls the onTick hook of the block's custom components every 40 to 80 ticks.
Permutations
A permutation is a Molang condition and the components that apply while it is true. Components in a permutation replace the root component with the same identifier.
from anvil.api.blocks.components import BlockLightEmission
from anvil.api.logic.molang import Query
block.server.permutation(Query.BlockState("lit")).add(
BlockLightEmission(value=12),
BlockMaterialInstance().add_instance(
spec=InstanceSpec(
blockbench="brass_lantern",
variations=[InstanceVariant(color="brass_lantern_lit")],
)
),
)
block.server.permutation(
Query.BlockState(PlacementDirectionTrait.CardinalDirection) == "north"
).add(BlockGeometry(blockbench="brass_lantern").set_n_way_visual_rotation(y="180"))
"permutations": [
{
"condition": "q.block_state('tut:lit')",
"components": {
"minecraft:light_emission": 12,
"minecraft:material_instances": {"*": {"texture": "tut:brass_lantern_lit"}}
}
},
{
"condition": "(q.block_state('minecraft:cardinal_direction') == 'north')",
"components": {
"minecraft:geometry": {"n_way_visual_rotation": {"y": "180"}, "identifier": "geometry.tut.brass_lantern"}
}
}
]
State names in Molang
Query.BlockState("lit") resolves the bare name to the block's own state. Pass a trait enum member for the game's states.
Rotation without permutations
BlockGeometry(...).set_n_way_visual_rotation(y="q.block_state('minecraft:cardinal_direction') == 'north' ? 180 : 0") rotates the model from Molang, so one geometry component can replace four permutations.
Custom Component With Parameters
Custom components carry data from Python to the Script API. Fields are declared like any dataclass field; Anvil validates them at export.
from typing import Annotated
from anvil.api.blocks.components import BlockCustomComponent
from anvil.lib.jsonmodel import InRange, json_field
class LanternToggle(BlockCustomComponent):
identifier = "lantern_toggle"
cooldown_ticks: Annotated[int, InRange(0, 200)] = json_field(default=10)
block.server.components.add(LanternToggle(cooldown_ticks=20))
The TypeScript side reads params.cooldown_ticks in onPlayerInteract and sets the tut:lit state with block.setPermutation(block.permutation.withState("tut:lit", true)). See Custom Components.
Sounds and the Item
from anvil.lib.jsonmodel.enums import BlockSoundGroup
from anvil.api.items.components import ItemBlockPlacer, ItemIcon
from anvil.api.pbr.texture_set import TextureComponents
block.client.sound_group(BlockSoundGroup.Chain)
item = block.item
item.server.description.set_menu_category(ItemCategory.Items, ItemGroups.Copper)
item.server.components.add(
ItemIcon(textures=TextureComponents(color="brass_lantern")),
ItemBlockPlacer(block=block, replace_block_item=True),
)
block.queue()
Note
ItemBlockPlacer(block=block, replace_block_item=True) makes the item the block's own item, so the icon from assets/textures/items/brass_lantern.png is what shows in the inventory.
Full Example — Brass Lantern
from typing import Annotated
from anvil.api.blocks.blocks import Block
from anvil.api.blocks.components import (
BlockCollisionBox,
BlockCustomComponent,
BlockDestructibleByMining,
BlockGeometry,
BlockLightEmission,
BlockMaterialInstance,
BlockPlacementFilter,
BlockSelectionBox,
BlockTick,
InstanceSpec,
InstanceVariant,
)
from anvil.lib.jsonmodel.enums import (
BlockFaceValues,
BlockSoundGroup,
ItemCategory,
ItemGroups,
PlacementDirectionTrait,
)
from anvil.api.items.components import ItemBlockPlacer, ItemIcon
from anvil.api.logic.molang import Query
from anvil.api.pbr.texture_set import TextureComponents
from anvil.api.vanilla.blocks import MinecraftBlockTypes
from anvil.lib.jsonmodel import InRange, json_field
class LanternToggle(BlockCustomComponent):
identifier = "lantern_toggle"
cooldown_ticks: Annotated[int, InRange(0, 200)] = json_field(default=10)
def brass_lantern():
block = Block("brass_lantern")
description = block.server.description
description.set_menu_category(ItemCategory.Items, ItemGroups.Copper)
description.add_state("lit", (False, True))
description.traits.set_placement_direction(
traits=[PlacementDirectionTrait.CardinalDirection]
)
block.server.components.add(
BlockGeometry(blockbench="brass_lantern"),
BlockMaterialInstance().add_instance(
spec=InstanceSpec(
blockbench="brass_lantern",
variations=[InstanceVariant(color="brass_lantern")],
)
),
BlockCollisionBox(origin=(-4, 0, -4), size=(8, 10, 8)),
BlockSelectionBox(origin=(-4, 0, -4), size=(8, 10, 8)),
BlockDestructibleByMining(seconds_to_destroy=0.5),
BlockPlacementFilter().add_condition(
allowed_faces=[BlockFaceValues.Up],
block_filter=[MinecraftBlockTypes.Stone(), MinecraftBlockTypes.Cobblestone()],
),
BlockTick(interval_range=(40, 80), looping=True),
LanternToggle(cooldown_ticks=20),
)
block.server.permutation(Query.BlockState("lit")).add(
BlockLightEmission(value=12),
BlockMaterialInstance().add_instance(
spec=InstanceSpec(
blockbench="brass_lantern",
variations=[InstanceVariant(color="brass_lantern_lit")],
)
),
)
block.server.permutation(
Query.BlockState(PlacementDirectionTrait.CardinalDirection) == "north"
).add(BlockGeometry(blockbench="brass_lantern").set_n_way_visual_rotation(y="180"))
block.client.sound_group(BlockSoundGroup.Chain)
item = block.item
item.server.description.set_menu_category(ItemCategory.Items, ItemGroups.Copper)
item.server.components.add(
ItemIcon(textures=TextureComponents(color="brass_lantern")),
ItemBlockPlacer(block=block, replace_block_item=True),
)
block.queue()
return block
BrassLantern = brass_lantern()
Blocks in other APIs
block.descriptor(lit=True) returns a BlockDescriptor with state values, accepted anywhere a vanilla MinecraftBlockTypes.X() is: loot tables, features, filters and recipes. To match blocks by tags only, write BlockDescriptor(tags=Query.AllTags(["stone"])).