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

FieldDefaultDescription
name(required)The block’s registered name.
collidabletrueWhether entities collide with this block.
breakabletrueWhether it can be destroyed at all. false makes it permanently indestructible regardless of the other breaking fields below.
tool-break-typesNoneWhich 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-required1Minimum tool power level required to break it.
time-to-break-ms1000How long it takes to break with a tool that meets power-required exactly (faster with a higher-power tool).
block-layer1Which 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.
itemtrueWhether 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-transmission1.0Fraction 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-selffalseShorthand for “drops one guaranteed copy of itself” — see Drop Table below. Ignored if drop-table is also set. Server-only.
phase-allfalsePhase 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 eachMovement 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-typeSolidOne of Solid, Liquid, Gas.
fluid-level0.0Fill percentage for fluid blocks.
fluid-flow-per-second0.0Fluid flow rate, in level per second. Server-only.
fluid-minimum-flow-threshold0.0Minimum 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-shapeFullFull 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-ms0Minimum 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-unsupportedfalseMakes 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-itemfalseIf true, opening the barrier removes one required-item from the player. Ignored without required-item. Server-only.
reveals-blockairThe 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-openfalseBarrier 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.yaml as 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-capability and required-item are set, the player needs both. consume-required-item removes 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 barriers breakable: false so 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-open is ignored for required-item barriers (a key must never be consumed without a deliberate interact); it logs a warning at load if combined with required-item or used without required-capability.
  • World-wide, first-come. Because opening broadcasts and persists, an auto-open barrier vanishes the instant the first qualifying player reaches it — for everyone. That fits “once you have the ability, these gates are just gone”; leave auto-open off 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-types breaks with anything, including a bare hand.
  • A block with tool-break-types: Axe and the default power-required: 1 can be broken by hand (a hand has Axe).
  • A block with tool-break-types: Pickaxe can never be broken by hand — only by a wielded item with pickaxe in its own tool-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:

LayerUse
0Background/walls — non-collidable.
1Foreground — 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.01.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
FieldDefaultDescription
typeFullFull (a normal full-size block) or Slab (a partial-height block).
divisions2Slab only. How many pieces a full block is divided into. Must be greater than 0.
count1Slab 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.
sideBottomSlab 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-interact runs when a player interacts with a placed instance of this block. Its handler receives (world, x, y, layer, actor_entity_id, actor), where actor is the interacting player’s entity object — use actor:has_item(...), actor:grant_capability(...), actor:send_message(...), etc. (Older five-argument handlers that ignore actor keep working.) interact-cooldown-ms sets a minimum time between interacts on the same cell (0 means no cooldown).
  • server-update runs 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