Wiki 9Minecraft Wiki 9Minecraft

Creating Multi-Blocks in Bedrock Add-Ons

How to build doors, tall plants, and large furniture that span more than one block space.

5Sections
2Code examples
4Known errors

A multi-block is any single gameplay object—such as a two-block-tall statue, a custom door, or a wide table—that occupies more than one grid space in the world. Because Minecraft Bedrock restricts block collision and selection boxes to a single 16x16x16 unit space, you cannot simply make a oversized model and expect solid physics in the adjacent spaces. Instead, you must combine a block state permutations setup with custom Script API components to place, link, and destroy the individual parts together.

Why Single Blocks Cannot Exceed One Grid Space

Every custom block in Bedrock Edition has two main bounding boxes: `minecraft:collision_box` (physical solidity) and `minecraft:selection_box` (the wireframe outline players see when aiming at the block). Both of these components clamp their maximum boundaries strictly to a single 16x16x16 pixel space, measured from `[-8, 0, -8]` to `[8, 16, 8]`.

Visual geometry (`minecraft:geometry`) is allowed to extend up to 30x30x30 pixels, slightly overflowing into neighboring blocks. However, any part of the model sticking out past the 16x16x16 boundary is purely visual. Players can walk right through the overhanging geometry, and they can place dirt or stone directly inside the upper half of your model. To make the upper or side space solid and interactive, a real block must occupy that coordinate.

ApproachHow It WorksWhen to Use It
Single Block with StateOne block file uses a custom state (like `wiki:part`) and permutations to swap geometry or visibility.2-block tall props, statues, and plants where both halves share the same hardness and loot.
Controller + Dummy BlockA main block has the full 30x30x30 visual model; invisible barrier-like blocks fill the surrounding spaces.Large furniture (tables, sofas) where only one geometry file should render to save performance.
Separate Block IdentifiersDistinct block definitions for each piece (e.g., `wiki:door_lower` and `wiki:door_upper`).Complex structures where each part needs completely different components or block states.

Single-Block Definition with Part States

For a standard two-block-tall object like a grandfather clock or a statue, using a single block definition with a custom block state is the cleanest method. The bottom half holds the full visual model, while the top half uses the built-in `minecraft:geometry.full_block` unit cube with its material instances set to an invisible texture, or a dedicated empty geometry.

In the block JSON below, the state `wiki:half` controls which part of the structure the block represents. The `minecraft:custom_components` array attaches our script logic (`wiki:tall_statue_logic`), which will handle checking for headroom when placed and breaking both halves when either is mined.

BP/blocks/tall_statue.json
{
  "format_version": "1.21.10",
  "minecraft:block": {
    "description": {
      "identifier": "wiki:tall_statue",
      "menu_category": {
        "category": "construction"
      },
      "states": {
        "wiki:half": ["lower", "upper"]
      },
      "traits": {
        "minecraft:placement_direction": {
          "enabled_states": ["minecraft:cardinal_direction"]
        }
      }
    },
    "components": {
      "minecraft:collision_box": true,
      "minecraft:selection_box": true,
      "minecraft:destructible_by_mining": {
        "seconds_to_destroy": 2.0
      },
      "minecraft:destructible_by_explosion": {
        "explosion_resistance": 6.0
      },
      "minecraft:custom_components": [
        "wiki:tall_statue_logic"
      ]
    },
    "permutations": [
      {
        "condition": "q.block_state('wiki:half') == 'lower'",
        "components": {
          "minecraft:geometry": "geometry.tall_statue",
          "minecraft:material_instances": {
            "*": {
              "texture": "tall_statue",
              "render_method": "alpha_test"
            }
          }
        }
      },
      {
        "condition": "q.block_state('wiki:half') == 'upper'",
        "components": {
          "minecraft:geometry": "geometry.empty_box",
          "minecraft:material_instances": {
            "*": {
              "texture": "tall_statue",
              "render_method": "alpha_test"
            }
          },
          "minecraft:loot": "loot_tables/empty.json"
        }
      },
      {
        "condition": "q.block_state('minecraft:cardinal_direction') == 'north'",
        "components": {
          "minecraft:transformation": { "rotation": [0, 0, 0] }
        }
      },
      {
        "condition": "q.block_state('minecraft:cardinal_direction') == 'south'",
        "components": {
          "minecraft:transformation": { "rotation": [0, 180, 0] }
        }
      },
      {
        "condition": "q.block_state('minecraft:cardinal_direction') == 'west'",
        "components": {
          "minecraft:transformation": { "rotation": [0, 90, 0] }
        }
      },
      {
        "condition": "q.block_state('minecraft:cardinal_direction') == 'east'",
        "components": {
          "minecraft:transformation": { "rotation": [0, 270, 0] }
        }
      }
    ]
  }
}

Handling Placement with Script API

Since Bedrock JSON events (`minecraft:on_placed`, `minecraft:on_player_destroyed`) were removed from modern format versions, multi-block assembly requires the `@minecraft/server` Script API. You register a custom block component during the `worldInitialize` event (or `startup` in newer API builds) and use the `beforeOnPlayerPlace` callback to validate the surrounding space before the block is even placed.

Inside `beforeOnPlayerPlace`, you must inspect the block above the target placement location. If that space is not air (or a liquid you want to allow replacing), you set `event.cancel = true`. This stops the player from placing the bottom half under a low ceiling where the top half would overwrite an existing block like bedrock or a chest.

BP/scripts/tall_statue.js
import { world, BlockPermutation } from "@minecraft/server";

world.beforeEvents.worldInitialize.subscribe((initEvent) => {
  initEvent.blockComponentRegistry.registerCustomComponent("wiki:tall_statue_logic", {
    beforeOnPlayerPlace(event) {
      const { block, permutationToPlace } = event;
      const blockAbove = block.above(1);

      if (!blockAbove || !blockAbove.isAir) {
        event.cancel = true;
        return;
      }

      const direction = permutationToPlace.getState("minecraft:cardinal_direction");
      const upperPerm = BlockPermutation.resolve("wiki:tall_statue", {
        "wiki:half": "upper",
        "minecraft:cardinal_direction": direction
      });

      blockAbove.setPermutation(upperPerm);
    },

    onPlayerDestroy(event) {
      const { block, destroyedBlockPermutation } = event;
      const half = destroyedBlockPermutation.getState("wiki:half");

      if (half === "lower") {
        const blockAbove = block.above(1);
        if (blockAbove && blockAbove.typeId === "wiki:tall_statue") {
          blockAbove.setType("minecraft:air");
        }
      } else if (half === "upper") {
        const blockBelow = block.below(1);
        if (blockBelow && blockBelow.typeId === "wiki:tall_statue") {
          const { x, y, z } = blockBelow.location;
          block.dimension.runCommand(`setblock ${x} ${y} ${z} air destroy`);
        }
      }
    }
  });
});

Synchronizing Destruction and Loot Drops

When a player mines either half of a multi-block, the remaining half must be removed immediately so it does not float in mid-air. In the `onPlayerDestroy` callback, you read `event.destroyedBlockPermutation`—not `event.block.permutation`, because by the time `onPlayerDestroy` fires, the mined block has already turned into air.

Pay special attention to loot drops when destroying the secondary part via script. In the JSON above, the `upper` permutation points its `minecraft:loot` component to an empty loot table (`loot_tables/empty.json`) so mining the top half does not directly drop a second statue. Instead, when the player breaks the `upper` half, the script runs `setblock ... air destroy` on the `lower` half, forcing the bottom block to break naturally and drop its normal item.

Piston Pushing and Explosion Caveats

Custom block components currently provide `onPlayerDestroy`, which only triggers when a player mines the block. It does not fire when a Creeper blows up half of your statue, or when a piston pushes one half out of alignment. For decorative furniture, you should prevent piston movement by adding the `minecraft:immovable` component or setting explosion resistance high enough to survive blasts if you cannot afford orphaned parts.

If your block must be destructible by explosions or support physics, there are two ways to clean up orphaned halves. You can subscribe to the global `world.afterEvents.blockExplode` event in your script to check adjacent coordinates when a `wiki:tall_statue` block is destroyed by TNT, or you can attach a slow `onTick` custom component to the block that periodically verifies its partner block is still intact.

Which versions this applies to

Applies to Bedrock Edition 1.20.80 and 1.21+. Block Custom Components and placement traits are stable and do not require any experimental toggles in world settings.

When it does not work

What you seeWhyThe fix
[Blocks][error]-wiki:tall_statue | minecraft:geometry: bone 'root' is outside of allowed boundsYour two-block-tall Blockbench model exceeds the strict 30x30x30 pixel geometry limit, or its pivot is offset too far into the second block space.Ensure all cubes in your model stay within 30 units of the origin, or split the visual model into two separate 16x16x16 geometries—one for the `lower` permutation and one for the `upper` permutation.
Placing the block near a ceiling replaces the ceiling block above itThe script places the upper block unconditionally without checking what is already at `block.above(1)`.Always check `if (!block.above(1)?.isAir)` inside `beforeOnPlayerPlace` and set `event.cancel = true` if the space is occupied.
TypeError: Cannot read properties of undefined (reading 'getState') in onPlayerDestroyYou tried to read the block state from `event.block.permutation.getState('wiki:half')` after the block was already broken and replaced by `minecraft:air`.Read the state from `event.destroyedBlockPermutation.getState('wiki:half')` instead, which preserves the state the block had right before it broke.
Upper half renders as a black and magenta missing texture cubeYou used `minecraft:geometry.full_block` for the invisible dummy half without an empty geometry or proper alpha-tested blank texture.Create a small empty geometry file (`geometry.empty_box`) in `RP/models/blocks/` with no cubes inside it, and reference that identifier in the `upper` permutation.

Reference, straight from the game files

Read out of the game itself, so the ids match the version you are building for.