Block Configuration
Register the placeable, world-generated blocks that make up terrain, walls, and fluids.
Blocks are the tiles that make up the world — terrain, walls, fluids, and any other placeable tile content. This is the catalog file that registers them. Server and client each load their own copy of this file independently (a few fields, noted below, are server-only).
File Location
Register the file in your mod’s manifest (mods/<YourMod>/<YourMod>.yaml):
block-config: block-config.yaml
Then create the file it points to, relative to your mod’s folder:
mods/<YourMod>/block-config.yaml
The file is a single list of block entries:
blocks:
- name: my_block
tool-break-types: pickaxe
Block names are automatically prefixed with your mod name unless you write one already containing a colon, so my_block becomes YourMod:my_block — this is the full name used anywhere a block is referenced (world generation, item drops, structures, Lua scripts).
Block IDs are assigned dynamically in load order, with one exception: Creation:air is always ID 0.
Top-level field names are validated on load: an unknown or misspelled key (for example time-to-brek-ms instead of time-to-break-ms) is a load-time error that skips that block, rather than being silently ignored. If a block doesn’t behave as expected, check the log for a parse error naming the offending field. The nested blocks are validated the same way — an unrecognized key inside a light, block-shape, particles, scripts, or drop-table entry block is also a load-time error naming the bad key, not a silently-ignored one (see Unknown Fields).
Minimal Example
blocks:
- name: my_ore_block
tool-break-types: pickaxe
power-required: 2
time-to-break-ms: 1500
drop-table:
drops:
- item: YourMod:my_ore_item
min-amount: 1
max-amount: 1
chance: 1.0
Fields
| Field | Default | Description |
|---|---|---|
name | (required) | The block’s registered name. |
collidable | true | Whether entities collide with this block. |
breakable | true | Whether it can be destroyed at all. false makes it permanently indestructible regardless of the other breaking fields below. |
tool-break-types | None | Which tool category or categories break it — a single value or a list. Omit for no restriction (breakable by any tool or a bare hand); use breakable: false for a permanent block. |
power-required | 1 | Minimum tool power level required to break it. |
time-to-break-ms | 1000 | How long it takes to break with a tool that meets power-required exactly (faster with a higher-power tool). |
block-layer | 1 | Which layer this block occupies. |
particles | (none) | Client-only. Ambient particle emission — a list of emitter groups, each with its own emit-random and optional only-emit-when-exposed-below. See the particles: block. |
item | true | Whether registering this block also registers its type: block item, making it obtainable and placeable. Set false for a block that must never exist in an inventory — see Block items. |
light | (none) | Makes the block emit light — same shape as Shared Blocks → light, plus an optional animated frames list. |
light-transmission | 1.0 | Fraction of light the block lets through: 1.0 passes light unattenuated, 0.0 blocks it completely. Point lights scale continuously (e.g. 0.75 costs 25% of the ray per block crossed); sky light only passes undimmed at exactly 1.0, and costs one level per block below that. |
particles | (none) | Ambient particle emitters for this block — see internal_docs/rendering.md’s Block Particles section (not yet published as a modder-facing doc). |
drop-table | (none) | Items dropped when broken — independent drops: and/or weighted groups:; see Drop Table below. Server-only. |
drops-self | false | Shorthand for “drops one guaranteed copy of itself” — see Drop Table below. Ignored if drop-table is also set. Server-only. |
phase-all | false | Phase in all four directions at once — see Directional Phasing. |
phase | (none) | A list of directions to phase, e.g. [up, down] — see Directional Phasing. |
drag (up, down, left, right) | 0.0 each | Movement drag applied to entities passing through the block, per direction — used for fluids. Each direction defaults independently, so you can set only the ones you need. Not an {x, y} pair: drag is genuinely per-direction (a fluid can resist sinking more than rising). |
block-matter-type | Solid | One of Solid, Liquid, Gas. |
fluid-level | 0.0 | Fill percentage for fluid blocks. |
fluid-flow-per-second | 0.0 | Fluid flow rate, in level per second. Server-only. |
fluid-minimum-flow-threshold | 0.0 | Minimum level difference before fluid flows between cells. Server-only. |
variant-of | (none) | Declares this block as a named variant of another block — e.g. snowy_grass is the snow variant of grass. A back-reference, registered into the base block’s variant table and looked up from Lua via get_block_variation. See Variant Of below. Server-only. |
block-shape | Full | Full or Slab — see Block Shape below. |
scripts.server-on-interact | (none) | A Lua hook run when a player interacts with a placed instance of this block. Server-only. |
interact-cooldown-ms | 0 | Minimum time between interacts on a single cell of this block. Server-only. |
scripts.server-update | (none) | A per-tick Lua hook run on every placed instance of this block. Server-only. |
falls-when-unsupported | false | Makes the block fall straight down when nothing solid is beneath it — see Falling Blocks below. Server-only. |
required-capability | (none) | Marks the block a sealed barrier. A player interacting with it opens it (permanently) only if they hold this capability. Server-only. |
required-item | (none) | Sealed-barrier requirement: the player must have this item in their inventory to open the barrier. Combined with required-capability by AND. Server-only. |
consume-required-item | false | If true, opening the barrier removes one required-item from the player. Ignored without required-item. Server-only. |
reveals-block | air | The block a sealed barrier becomes when opened (default: cleared to air). Server-only. |
sealed-message | (none) | Message shown to a player who interacts with a sealed barrier without meeting its requirement. Server-only. |
auto-open | false | Barrier opens automatically for a qualifying player instead of on interact — see Sealed Barriers. Only applies to pure required-capability barriers. Server-only. |
Block items
A block automatically gets a matching item — same name, type: block — so it can be picked up and placed without you writing anything in item-config.yaml. Declaring a block is enough to make it obtainable.
Write an entry in item-config.yaml only when the item needs more than the defaults — a display-name, a tooltip, a material-type for crafting, or a light on the dropped stack. An explicit entry with the same name replaces the generated one entirely, so you keep full control when you want it.
# block-config.yaml — this alone gives you a placeable, obtainable block
blocks:
- name: marble
tool-break-types: pickaxe
drops-self: true
Set item: false for a block that should never be holdable — a marker or internal block placed only by world generation or Lua:
- name: my_internal_marker
item: false
You still need an item texture. The generated item is a config entry, not an asset — add the block’s icon to
assets/textures/items/items.yamlas normal (see Item Texture Configuration).
Interactables work the same way, via their own item field.
Falling Blocks
Setting falls-when-unsupported: true makes a placed block (sand, gravel, and the like) fall straight down whenever the cell directly below it is not solid support:
- name: sand
tool-break-types: shovel
falls-when-unsupported: true
Behaviour:
- Vertical only. The block drops straight down one cell at a time and settles as soon as it lands on a solid block. There is no sideways sliding or pile-forming.
- Fluids are destroyed. A falling block falls through fluid, erasing the fluid in each cell it passes.
- Runtime only. Naturally world-generated blocks of a falling type do not fall on their own — a falling block only starts falling when it (or the block beneath it) is placed or removed during play. This matches Minecraft: generated overhangs of sand stay put until disturbed.
- Foreground layer only. Falling applies to the foreground collision layer (
block-layer: 1); it has no effect on background/wall layers.
Sealed Barriers (capability-gated)
A sealed barrier is a wall block that a player can only pass once they meet a requirement — the Metroidvania “locked door” primitive. A block becomes a barrier as soon as it declares required-capability and/or required-item:
# Opens only for a player who holds the `storm_ward` capability.
- name: sealed_stormward
breakable: false
required-capability: storm_ward
reveals-block: air # what it becomes when opened (default: air)
sealed-message: "The way is sealed."
# Opens for a player carrying a `void_key` item, consuming one on use.
- name: sealed_vault_door
breakable: false
required-item: void_key
consume-required-item: true
Behaviour:
- Interact to open. When a player interacts (uses) a barrier and meets the requirement, the block is permanently swapped to its
reveals-block(default: cleared to air). The change is broadcast to every client and saved with the world, so it stays open. - Requirements combine by AND. If both
required-capabilityandrequired-itemare set, the player needs both.consume-required-itemremoves one matching item on a successful open. - Gates who opens, not who passes. Once opened the way is open for everyone (co-op friendly); the requirement controls who can open it.
- Feedback. A player who interacts without qualifying is sent
sealed-message(or a default message if unset). - Foreground layer only, checked on the collision layer (
block-layer: 1). Make barriersbreakable: falseso they can only be removed by opening them.
Auto-opening barriers
Interacting with every block of a large sealed wall is tedious. Set auto-open: true and the barrier clears on its own for a qualifying player — no interact:
- name: sealed_stormward
breakable: false
required-capability: storm_ward
auto-open: true
It triggers both when a qualifying player streams the chunk (walks into the area with the capability) and the moment they gain the capability while standing among already-loaded barriers — so a whole wall falls away at once. Notes:
- Capability barriers only.
auto-openis ignored forrequired-itembarriers (a key must never be consumed without a deliberate interact); it logs a warning at load if combined withrequired-itemor used withoutrequired-capability. - World-wide, first-come. Because opening broadcasts and persists, an
auto-openbarrier vanishes the instant the first qualifying player reaches it — for everyone. That fits “once you have the ability, these gates are just gone”; leaveauto-openoff if you want a barrier the player must deliberately open.
Capabilities are free-form string ids. Grant them from Lua with player:grant_capability("storm_ward") (see Server Entity API) — typically from a boss-death, item-use, mount, or interactable script. required-item refers to an item name (namespaced to the block’s mod, like drop-table items).
Placement. Barriers are ordinary blocks, so place them by authoring them into a structure or via world generation — no special placement mode is needed.
tool-break-types
One tool category, or a list of them — each one of shovel, pickaxe, axe, shears — matched against the wielded tool’s own tool-types list. A block breaks if the wielded tool matches any listed category. This is the same shape interactables and trees use, so a single value (tool-break-types: Shovel) and a list (tool-break-types: [Pickaxe, Axe]) are both accepted.
Those four are what the base game ships, not the complete set — tool categories are an open registry, so tool-break-types: hammer is a valid requirement as soon as some item declares tool-types: hammer.
The consequence to know is what a typo does. tool-break-types: pickaxee is not a load error — it names a category, just one no item has — so the block becomes breakable by nothing. That is deliberately the safe direction: it used to resolve to “no tool requirement”, leaving a pickaxe-only block breakable by hand, which is both the opposite of what was written and indistinguishable from omitting the field. The server also warns at load, naming the block and the unmatched category.
Leaving this unset means “no tool requirement” — the block breaks with a bare hand or any tool (still subject to power-required). List one or more categories to restrict which tools work; use breakable: false to make a block permanent. Bare hands are internally treated as a tool with tool-types: [Shovel, Axe, Shears] (no pickaxe) at power level 1, so:
- A block with no
tool-break-typesbreaks with anything, including a bare hand. - A block with
tool-break-types: Axeand the defaultpower-required: 1can be broken by hand (a hand hasAxe). - A block with
tool-break-types: Pickaxecan never be broken by hand — only by a wielded item withpickaxein its owntool-types.
tool-break-types: pickaxe # single category
# or
tool-break-types: [pickaxe, axe] # any of several
block-layer
Which of the world’s layers this block occupies:
| Layer | Use |
|---|---|
0 | Background/walls — non-collidable. |
1 | Foreground — default. Collision, entities, players. Fluids belong here too, so entities passing through them pick up the block’s drag-* physics. |
2+ | Overlay — effects drawn on top of the foreground. |
Drop Table
Blocks use the same shared drop-table block as interactables, mobs, trees, and mounts. It has two optional parts — independent drops: and weighted pick-one groups::
drop-table:
groups: # weighted pick-one — each group yields at most one entry
- entries:
- item: YourMod:dirt
min-amount: 1
max-amount: 1
weight: 0.9
- item: YourMod:stone
min-amount: 1
max-amount: 1
weight: 0.1
drops: # optional: independent rolls (e.g. a rare bonus drop)
- item: YourMod:flint
chance: 0.1
Each group rolls once, picking one entry weighted by weight relative to the group’s total (the classic “always drop the block, weighted by variant” pattern); each drops entry rolls independently on its own chance (a 0.0–1.0 probability). The two keys are different words because they behave differently — a group weight is a relative weight (a lone entry always wins), a drops chance is a probability. See the shared Drop Table reference for full semantics, and Drop Table vs Loot Table for how this differs from standalone Loot Tables.
Shorthand: drops-self
The most common case — a block that drops exactly one guaranteed copy of itself — has a one-line shorthand:
- name: stone
tool-break-types: pickaxe
drops-self: true
drops-self: true is exactly equivalent to writing:
drop-table:
groups:
- entries:
- item: YourMod:stone # this block's own full name
min-amount: 1
max-amount: 1
weight: 1.0
It requires an item registered under the block’s own full name (blocks and items share the Mod:name namespace) — the same requirement as spelling the drop table out by hand. If you set both drops-self and an explicit drop-table, the drop-table wins and a warning is logged.
Variant Of
Registers this block as an alternate visual form of another already-defined block — for example, a snow-covered variant of grass:
- name: snowy_grass
# ...this block's own fields...
variant-of:
- base-block-name: grass
variant-name: snow
base-block-name must be defined somewhere the loader can see it — in this same file, or in a dependency loaded earlier. Order within a file doesn’t matter: a variant may be listed before or after its base block, since variations are resolved after every block in the file has been registered. At runtime, code can look up snowy_grass’s ID via GRASS’s ID plus the variant name SNOW, without either block needing to know about the other beyond this declaration.
Directional Phasing
Controls which directions a block can visually “phase” through a neighbor (used for logs, leaves, platforms, and other blocks that render flush against adjacent tiles). There are two interchangeable ways to set it — a direction is phased if either of them enables it, so you can mix them freely:
# 1. The most common case — all four directions:
phase-all: true
# 2. An explicit subset (directions: up, down, left, right):
phase: [up, down]
phase-all: true is the concise form for the usual “phase in every direction” case; phase: [...] is for an explicit subset. Both forms produce the same underlying per-direction flags.
Block Shape
block-shape:
type: slab
divisions: 8
side: bottom
count: 1
| Field | Default | Description |
|---|---|---|
type | Full | Full (a normal full-size block) or Slab (a partial-height block). |
divisions | 2 | Slab only. How many pieces a full block is divided into. Must be greater than 0. |
count | 1 | Slab only. How many of those divisions the slab fills. Must be greater than 0. E.g. divisions: 8, count: 1 is a one-eighth-thick slab. |
side | Bottom | Slab only. Which edge of the cell the slab sits against: Top, Bottom, Left, or Right. See the note below — this does not control which way a player-placed slab faces. |
These fields set the shape a block starts with wherever it is placed without a player aiming at it — world generation, Lua, and similar.
side and player placement: when a player places a slab, its side is always taken from where the cursor sits within the target cell — the lower half of the cell gives a Bottom slab, the upper half gives Top, and with horizontal placement toggled off the left/right halves give Left/Right. The configured side is overridden and has no effect on that placement. So setting side: Top does not make a block place as a top slab; it only affects instances placed by world generation or a mod script. divisions and count are always used as configured.
Animated Light Frames
If a block’s light block declares frames, its emitted light pulses in step with its texture animation instead of staying static:
light:
magnitude: 3.0
color: { r: 1.0, g: 0.9, b: 0.4, a: 1.0 }
frames:
- magnitude: 4.0
intensity: 0.8
- magnitude: 3.0
Each frame entry corresponds to one frame of the block’s texture animation (see the block’s texture config). magnitude/intensity are both optional per frame and fall back to the light’s own base magnitude/intensity when omitted. Declaring any frames forces the light to be dynamic rather than static — reserve this for a handful of accent lights, not bulk content, since dynamic lights are considerably more expensive to render.
Lua Hooks
Both hooks are declared under a single scripts: map, keyed by the event name, with the bare path to a .lua file that returns its handler function (the same shape as items’ server-on-primary-use):
scripts:
server-on-interact: /blocks/scripts/my_block_interact.lua
server-update: /blocks/scripts/my_block_tick.lua
interact-cooldown-ms: 200
server-on-interactruns when a player interacts with a placed instance of this block. Its handler receives(world, x, y, layer, actor_entity_id, actor), whereactoris the interacting player’s entity object — useactor:has_item(...),actor:grant_capability(...),actor:send_message(...), etc. (Older five-argument handlers that ignoreactorkeep working.)interact-cooldown-mssets a minimum time between interacts on the same cell (0means no cooldown).server-updateruns every server tick for every placed instance of this block. Use sparingly — a block with this hook joins the world’s ticking-blocks registry, so placing many instances means many per-tick Lua calls.
Complete Example
Based on a real light-emitting, interactable block:
blocks:
- name: glow_rock
tool-break-types: pickaxe
time-to-break-ms: 1000
power-required: 2
scripts:
server-on-interact: /blocks/scripts/glow_rock_interact.lua
interact-cooldown-ms: 200
light:
magnitude: 3.0
color:
r: 1.0
g: 0.945
b: 0.4627
a: 0.9
drop-off-distance: 4.0
is-static: true
drop-table:
drops:
- item: YourMod:glow_rock
min-amount: 1
max-amount: 1
chance: 1.0
And a fluid block:
- name: water
collidable: false
breakable: false
block-layer: 1
drag:
up: 0.5
down: 0.5
left: 0.8
right: 0.8
block-matter-type: liquid
fluid-level: 1.0
fluid-flow-per-second: 5.0
fluid-minimum-flow-threshold: 0.5