Architecture of a Bedrock crop
A working crop requires four distinct pieces working together across your behavior pack (BP) and resource pack (RP). First, the block definition declares an integer state (for example, wiki:growth_stage from 0 to 3) and uses permutations to swap textures, loot tables, and selection boxes at each stage. Second, a plant-style geometry—either the built-in cross model or a custom Blockbench model—renders flat planes with transparency.
Third, because the legacy JSON event system was removed in format version 1.20.80, growth logic now lives in TypeScript or JavaScript using the @minecraft/server module. You register a block custom component that listens to onRandomTick to increment the state naturally, and onPlayerInteract to advance the state when the player holds bonemeal. Finally, two separate loot tables handle what drops when a player breaks an immature sprout versus a fully grown plant.
| Component / Feature | What it does | Notes |
|---|---|---|
| states | Tracks the current growth stage as an integer on the block | Defined in the description object; maximum 16 total state permutations per trait |
| minecraft:placement_filter | Restricts placement to farmland or dirt and pops the crop off if the soil breaks | Checks the block directly below using block_filter |
| minecraft:collision_box | Controls physical collision with entities | Set to false so players and mobs walk straight through the crop |
| minecraft:selection_box | Defines the wireframe outline players target to break or interact | Usually grows taller in later permutations |
| minecraft:custom_components | Binds Script API listeners for random ticks and player interaction | Stable since 1.21.10; replaces old Holiday Creator Features events |
Behavior pack block definition
Start by creating the block JSON in your behavior pack. To make the block look and act like a plant, disable collision, set the render method to alpha_test so transparent pixels on your texture are cut out cleanly, and make it instant-break with minecraft:destructible_by_mining. The identifier minecraft:geometry.cross is built into vanilla Bedrock and arranges two intersecting planes diagonally across the block space, saving you from having to supply a custom geometry file for simple crops.
Use permutations to override the material instances, selection box height, and loot table at each growth stage. Notice how stages 0 through 2 share a seed loot table on the base components block, while stage 3 overrides minecraft:loot in its permutation to point to the mature harvest table. The placement filter ensures players can only plant the crop on top of vanilla farmland.
{
"format_version": "1.21.10",
"minecraft:block": {
"description": {
"identifier": "wiki:tomato_crop",
"states": {
"wiki:growth": [0, 1, 2, 3]
}
},
"components": {
"minecraft:collision_box": false,
"minecraft:destructible_by_mining": {
"seconds_to_destroy": 0.0
},
"minecraft:destructible_by_explosion": {
"explosion_resistance": 0.0
},
"minecraft:geometry": "minecraft:geometry.cross",
"minecraft:light_dampening": 0,
"minecraft:loot": "loot_tables/blocks/tomato_crop_seeds.json",
"minecraft:placement_filter": {
"conditions": [
{
"allowed_faces": ["up"],
"block_filter": ["minecraft:farmland"]
}
]
},
"minecraft:custom_components": [
"wiki:crop_growth"
]
},
"permutations": [
{
"condition": "q.block_state('wiki:growth') == 0",
"components": {
"minecraft:selection_box": {
"origin": [-6, 0, -6],
"size": [12, 4, 12]
},
"minecraft:material_instances": {
"*": {
"texture": "tomato_stage_0",
"render_method": "alpha_test",
"face_dimming": false,
"ambient_occlusion": false
}
}
}
},
{
"condition": "q.block_state('wiki:growth') == 1",
"components": {
"minecraft:selection_box": {
"origin": [-6, 0, -6],
"size": [12, 8, 12]
},
"minecraft:material_instances": {
"*": {
"texture": "tomato_stage_1",
"render_method": "alpha_test",
"face_dimming": false,
"ambient_occlusion": false
}
}
}
},
{
"condition": "q.block_state('wiki:growth') == 2",
"components": {
"minecraft:selection_box": {
"origin": [-6, 0, -6],
"size": [12, 12, 12]
},
"minecraft:material_instances": {
"*": {
"texture": "tomato_stage_2",
"render_method": "alpha_test",
"face_dimming": false,
"ambient_occlusion": false
}
}
}
},
{
"condition": "q.block_state('wiki:growth') == 3",
"components": {
"minecraft:loot": "loot_tables/blocks/tomato_crop_mature.json",
"minecraft:selection_box": {
"origin": [-6, 0, -6],
"size": [12, 16, 12]
},
"minecraft:material_instances": {
"*": {
"texture": "tomato_stage_3",
"render_method": "alpha_test",
"face_dimming": false,
"ambient_occlusion": false
}
}
}
}
]
}
}Growth and bonemeal via Script API
To make the crop actually advance through its wiki:growth state, register the wiki:crop_growth custom component during the worldInitialize before-event in your behavior pack script. Every block with an onRandomTick handler is automatically queued by the game engine to receive random ticks at the same rate as vanilla crops, without needing any extra ticking component in the block JSON.
In the onPlayerInteract callback, inspect the item in the player's mainhand using EntityEquippableComponent. If the item is minecraft:bone_meal and the growth state is below the maximum of 3, apply the new permutation with BlockPermutation.withState, spawn the vanilla happy villager particle at the block's center, and decrement the item stack if the player is not in Creative mode.
import { world, EquipmentSlot, GameMode } from "@minecraft/server";
world.beforeEvents.worldInitialize.subscribe((event) => {
event.blockComponentRegistry.registerCustomComponent("wiki:crop_growth", {
onRandomTick(e) {
const growth = e.block.permutation.getState("wiki:growth");
if (typeof growth === "number" && growth < 3) {
if (Math.random() < 0.35) {
e.block.setPermutation(e.block.permutation.withState("wiki:growth", growth + 1));
}
}
},
onPlayerInteract(e) {
const { block, player, dimension } = e;
if (!player) return;
const growth = block.permutation.getState("wiki:growth");
if (typeof growth !== "number" || growth >= 3) return;
const equippable = player.getComponent("minecraft:equippable");
const mainhand = equippable?.getEquipmentSlot(EquipmentSlot.Mainhand);
if (!mainhand?.hasItem() || mainhand.typeId !== "minecraft:bone_meal") return;
block.setPermutation(block.permutation.withState("wiki:growth", growth + 1));
dimension.spawnParticle("minecraft:crop_growth_emitter", block.center());
dimension.playSound("item.bone_meal.use", block.center());
if (player.getGameMode() !== GameMode.creative) {
if (mainhand.amount > 1) {
mainhand.amount -= 1;
} else {
mainhand.setItem(undefined);
}
}
}
});
});Seeds item and terrain textures
Players normally plant crops from a seed item rather than placing the block item directly. Create a custom item with the minecraft:block_placer component configured to place wiki:tomato_crop on top of farmland. Setting replace_block_item to true only makes sense if the item shares the exact same identifier as the block; for a separate seed item, leave that out and let the block's seed loot table drop your custom seed identifier.
In your resource pack, map each short texture name used in minecraft:material_instances (tomato_stage_0 through tomato_stage_3) inside RP/textures/terrain_texture.json. Make sure your PNG files have a transparent background, not solid white or black, so the alpha_test render method cuts out the empty space around the leaves.
{
"format_version": "1.21.10",
"minecraft:item": {
"description": {
"identifier": "wiki:tomato_seeds",
"menu_category": {
"category": "nature",
"group": "itemGroup.name.seed"
}
},
"components": {
"minecraft:icon": "tomato_seeds",
"minecraft:block_placer": {
"block": "wiki:tomato_crop",
"use_on": [
"minecraft:farmland"
]
}
}
}
}Harvest loot tables
A crop needs two loot tables in BP/loot_tables/blocks/: one for immature stages that simply returns a single seed, and one for the final permutation that drops both the harvested produce and one to three seeds. Putting the immature loot table on the base components list and overriding it only in the final permutation keeps the block JSON compact.
{
"pools": [
{
"rolls": 1,
"entries": [
{
"type": "item",
"name": "wiki:tomato",
"functions": [
{
"function": "set_count",
"count": {
"min": 2,
"max": 4
}
}
]
}
]
},
{
"rolls": 1,
"entries": [
{
"type": "item",
"name": "wiki:tomato_seeds",
"functions": [
{
"function": "set_count",
"count": {
"min": 1,
"max": 3
}
}
]
}
]
}
]
}Which versions this applies to
Applies to Minecraft Bedrock 1.21.10 and newer. No experimental toggles are required; minecraft:custom_components, minecraft:geometry.cross, and the worldInitialize registry in @minecraft/server (1.12.0+) are fully stable.
When it does not work
| What you see | Why | The fix |
|---|---|---|
| Content Log Error: [Blocks][error]-wiki:tomato_crop | minecraft:custom_components - failed to find custom component 'wiki:crop_growth' | Your BP/manifest.json is missing the script module dependency for @minecraft/server, the entry script path is wrong, or registerCustomComponent was called outside the world.beforeEvents.worldInitialize listener. | Add the "script" module and "@minecraft/server" dependency to BP/manifest.json, verify the entry file path matches BP/scripts/main.js, and register the component inside worldInitialize. |
| The crop renders with black or opaque white squares filling the empty space around the plant. | The default render_method on minecraft:material_instances is opaque, which ignores the alpha channel of your PNG texture. | Set "render_method": "alpha_test" inside every material instance in your block's permutations, and ensure "minecraft:light_dampening": 0 is set on the base components so the block does not cast a dark shadow on itself. |
| The crop looks abnormally dark on one side or shades the farmland underneath it black. | Cross-plane geometry calculates standard block face lighting and ambient occlusion unless told otherwise. | Add "face_dimming": false and "ambient_occlusion": false to the "*" material instance in each permutation, alongside "minecraft:light_dampening": 0. |
Reference, straight from the game files
Read out of the game itself, so the ids match the version you are building for.
