Why Did My Old Custom Blocks and Items Stop Working?
For several years, custom blocks and items relied on the Holiday Creator Features toggle to execute logic directly inside their behaviour JSON files. This workflow centered around an events block and inline trigger components that ran slash commands, mutated block states, or damaged tools whenever a player interacted with a block or swung an item. In August 2024, with the rollout of Bedrock 1.21.20, Mojang completed the deprecation of this system and removed the experimental toggle entirely.
If you load an older add-on in 1.21.20 or later, any block or item utilizing the legacy format version alongside JSON event definitions will either fail to parse in the content log or load into the world as an inert object with no interactive behaviour. The engine no longer reads JSON event trees inside blocks or items.
To restore functionality without any experimental toggles, you must upgrade your files to format version 1.21.10 or 1.21.20 and migrate your event logic to TypeScript or JavaScript using the @minecraft/server Script API. Instead of writing logic inside the JSON file, you now attach a custom component identifier to the block or item and register its behaviour during the world initialization event in your script entry file.
| Legacy Feature (Removed) | Modern Stable Equivalent | Notes |
|---|---|---|
| JSON events object | Scripted Custom Components | Registered via worldInitialize in @minecraft/server. |
| Inline run_command triggers | Dimension.runCommand() or API methods | Direct API manipulation (like setting states or spawning items) is preferred over running commands. |
| Holiday Creator Features toggle | None required | Custom blocks and items with scripted components work in stable worlds with no experiments enabled. |
| Legacy damage_item event action | ItemDurabilityComponent in script | Handled by modifying the durability component inside the item's ItemStack. |
How Do I Replace Block Trigger Components with Custom Components?
A frequent question during migration is how to handle player interactions, block breaking, or scheduled ticking now that inline triggers are gone. In the current architecture, the behaviour JSON file is strictly declarative. It defines physical properties, states, permutations, and a list of custom component strings inside the minecraft:custom_components array.
When you add a namespaced string to minecraft:custom_components, you are telling the game engine to forward lifecycle events for that block to the corresponding handler registered in your scripting environment. For example, if you want a block to toggle a boolean state when a player right-clicks or taps it, you define the state and its visual permutations in the JSON file, and list your custom component identifier.
Notice how clean the JSON becomes under this pattern. All the physical traits—such as mining time, collision, and geometry—stay in the JSON, while the conditional state toggling is moved out. You can also place minecraft:custom_components inside individual permutations if you only want a script handler to fire when the block is in a specific state.
{
"format_version": "1.21.20",
"minecraft:block": {
"description": {
"identifier": "wiki:copper_lamp",
"menu_category": {
"category": "construction"
},
"states": {
"wiki:lit": [
false,
true
]
}
},
"components": {
"minecraft:destructible_by_mining": {
"seconds_to_destroy": 2.0
},
"minecraft:destructible_by_explosion": {
"explosion_resistance": 6.0
},
"minecraft:geometry": "minecraft:geometry.full_block",
"minecraft:material_instances": {
"*": {
"texture": "copper_lamp_off",
"render_method": "opaque"
}
},
"minecraft:custom_components": [
"wiki:lamp_toggle"
]
},
"permutations": [
{
"condition": "q.block_state('wiki:lit') == true",
"components": {
"minecraft:light_emission": 15,
"minecraft:material_instances": {
"*": {
"texture": "copper_lamp_on",
"render_method": "opaque"
}
}
}
}
]
}
}How Do I Register Block and Item Components in Script?
Once your JSON files reference your custom component identifiers, you frequently need to know where and when to bind the JavaScript code to those names. The registration must happen inside the worldInitialize before-event provided by the @minecraft/server module. You cannot register custom components after the world has finished initializing or inside a standard system tick loop.
The event data passed into worldInitialize exposes two registries: blockComponentRegistry and itemComponentRegistry. Each has a registerCustomComponent method that accepts two arguments: the exact namespaced identifier string used in your JSON file, and an object containing the lifecycle methods you wish to implement.
For blocks, the supported lifecycle methods include onPlayerInteract, onPlayerDestroy, onPlace, onTick, onStepOn, onStepOff, onFallOn, and onRandomTick. For items, the supported methods are onUse, onUseOn, onCompleteUse, onHitEntity, onMineBlock, and onConsume. Below is the complete script needed to power the lamp block from the previous section alongside a custom wrench item.
import { world } from "@minecraft/server";
world.beforeEvents.worldInitialize.subscribe((eventData) => {
eventData.blockComponentRegistry.registerCustomComponent("wiki:lamp_toggle", {
onPlayerInteract(event) {
const { block, dimension } = event;
const permutation = block.permutation;
const currentState = permutation.getState("wiki:lit");
block.setPermutation(permutation.withState("wiki:lit", !currentState));
dimension.playSound("random.click", block.location);
}
});
eventData.itemComponentRegistry.registerCustomComponent("wiki:wrench_use", {
onUse(event) {
const { source } = event;
source.sendMessage("Wrench calibrated.");
}
});
});How Do I Set Up Custom Item Behaviour and Durability Now?
Another major point of confusion around the August 2024 updates is how custom items handle right-click usage, cooldowns, and durability loss. In older versions, creators had to combine chargeable or food components with complex event chains to detect a right-click, and durability had to be manually decremented using an event action on every hit or block break.
In the modern format, these components work together much more naturally. If you want an item to trigger its custom component's onUse method when the player right-clicks or long-presses the screen, you must still include the minecraft:use_modifiers component in the item's JSON definition. Without minecraft:use_modifiers, the client does not recognize the item as usable in the air, and the onUse script hook will not fire.
For tools and weapons, minecraft:durability now handles standard wear and tear automatically when combined with components like minecraft:digger or minecraft:damage. If you also need to reduce durability during a scripted interaction—such as using a wrench on a block—you can query the minecraft:durability component on the ItemStack in your script and increase its damage value directly.
{
"format_version": "1.21.20",
"minecraft:item": {
"description": {
"identifier": "wiki:copper_wrench",
"menu_category": {
"category": "equipment"
}
},
"components": {
"minecraft:max_stack_size": 1,
"minecraft:hand_equipped": true,
"minecraft:durability": {
"max_durability": 250
},
"minecraft:icon": "copper_wrench",
"minecraft:use_modifiers": {
"use_duration": 0.1,
"movement_modifier": 1.0
},
"minecraft:cooldown": {
"category": "wiki_wrench",
"duration": 1.0
},
"minecraft:custom_components": [
"wiki:wrench_use"
]
}
}
}Why Are My Custom Block Models Throwing Culling or Box Errors?
Beyond scripting changes, the 1.21.x updates tightened validation around block geometry, face culling, and bounding boxes. Creators frequently ask why blocks with custom Blockbench models either fail to load or display transparent gaps on adjacent vanilla blocks.
First, when using a custom model instead of a full block, you should explicitly specify the minecraft:geometry component using the object syntax rather than just a string if you intend to use bone visibility or culling rules. If your custom block is smaller than a standard 16x16x16 unit cube—such as a lantern, pillar, or table—it will still cull the faces of adjacent solid blocks by default unless you configure its occlusion behaviour or use a non-opaque render method in minecraft:material_instances.
Second, the engine strictly enforces boundaries on minecraft:collision_box and minecraft:selection_box. Both components define their bounds using an origin array (measured in pixels from -8 to 8 on the X and Z axes, and 0 to 16 on the Y axis) and a size array. Your combined origin plus size cannot exceed the 16x16x16 block volume; even a fraction of a pixel outside that boundary will cause the entire block definition to be rejected at load time.
| Component | What it does | Notes |
|---|---|---|
| minecraft:geometry | Assigns the 3D model from the resource pack | Use "minecraft:geometry.full_block" for standard cubes, or a custom geo identifier. |
| minecraft:material_instances | Maps short texture names and render methods to faces or materials | Set render_method to "alpha_test" or "blend" for non-solid blocks to prevent x-ray culling issues. |
| minecraft:collision_box | Defines the physical hitbox entities collide with | Accepts a boolean or an object with origin [-8, 0, -8] and size [16, 16, 16] limits. |
| minecraft:selection_box | Defines the wireframe outline players target | Must stay strictly within the 16x16x16 pixel block boundary. |
| minecraft:transformation | Rotates, scales, or translates the block model and boxes | Rotations must be in 90-degree increments. |
Which versions this applies to
Applies to Bedrock Edition 1.21.20 and newer. Custom components for blocks and items are stable in these versions and require no experimental toggles. Requires @minecraft/server 1.13.0 or higher in your behaviour pack manifest.
When it does not work
| What you see | Why | The fix |
|---|---|---|
| Content Log Error: [Block] Failed to find custom component 'wiki:lamp_toggle' | The block JSON references a custom component in minecraft:custom_components, but the script either did not run, threw a syntax error before registration, or registered the component outside the worldInitialize event. | Ensure your BP/manifest.json has a valid script entry point and dependency on @minecraft/server, and verify that blockComponentRegistry.registerCustomComponent is called inside world.beforeEvents.worldInitialize.subscribe(). |
| Item's onUse custom component never triggers when right-clicking in the air | The item definition in the behaviour pack is missing the minecraft:use_modifiers component. Without it, the game only checks for block interactions (onUseOn) and ignores general right-click use. | Add "minecraft:use_modifiers" with a valid "use_duration" (such as 0.1 or 1.6) to the components list in your item JSON file. |
| Block loads into the world with an update/checkerboard texture and no collision | The block JSON is either using an outdated format_version with deprecated Holiday Creator Features components (like minecraft:on_interact), or its collision_box exceeds the 16x16x16 limit. | Update format_version to "1.21.20", remove any legacy event or trigger components, and check that your collision_box origin plus size values stay between [-8, 0, -8] and [8, 16, 8]. |
| Placing a custom modeled block turns the sides of adjacent dirt or stone blocks invisible | By default, blocks with the "opaque" render method tell the rendering engine to cull adjacent block faces to save performance, even if the custom geometry does not fill the whole 16x16x16 space. | Change "render_method" inside "minecraft:material_instances" from "opaque" to "alpha_test", or configure custom culling rules for the geometry. |
Reference, straight from the game files
Read out of the game itself, so the ids match the version you are building for.
