The State of Custom Biomes in Late 2024
The single largest point of confusion in late 2024 world generation is the status of custom biomes. When 1.21.20 shipped, Mojang removed the legacy Holiday Creator Features toggle, which previously housed early experimental biome components. In its place, work toward a revised data-driven biome infrastructure sits behind the Custom Biomes experimental toggle. If you are building a pack meant for stable, non-experimental worlds, you cannot inject brand new biome identifiers into the Overworld generation map.
However, the inability to register new biomes in stable releases does not mean you cannot alter how the world looks and populates. Developers have repeatedly emphasized that feature rules—the JSON files that dictate where ores, trees, and structures spawn—remain completely stable and do not require any experimental toggles. Instead of creating a new biome to host your custom content, the standard production pattern is to target existing vanilla biomes using biome filter tags.
If you do turn on the Custom Biomes experiment in 1.21.40 or later to test upcoming capabilities, remember that the schema is actively shifting. Components that dictated noise generation parameters in 1.16 through 1.19 are largely deprecated or ignored by the modern Caves and Cliffs terrain shaper. For stable production packs in late 2024, treat vanilla biome JSON files purely as read-only references for their tags, and drive all your custom terrain decoration through the features and feature_rules directories.
| System | Toggle Required (Late 2024) | Practical Capabilities |
|---|---|---|
| Feature Rules | None (Stable) | Attach custom features to vanilla biomes using tag filters and pass ordering. |
| Features | None (Stable) | Define ore blobs, single blocks, scatter arrays, and structure template placements. |
| Jigsaw Structures | None / Varies by version | Data-driven jigsaw pools saw rollout in 1.21.20+, though complex terrain adaptation remains tricky. |
| Custom Biomes | Custom Biomes Experiment | Registering new biome IDs and overriding surface builders; subject to breaking changes. |
Feature Rules and the Placement Pass Pipeline
A frequent question in worldgen Q&A sessions is why a custom tree or structure feature fails to detect the blocks placed by another custom feature in the same chunk. The answer almost always comes down to the placement_pass string inside your feature rule. Bedrock does not evaluate all feature rules simultaneously; it steps through a rigid, hardcoded sequence of passes for every chunk being generated.
When a chunk enters the decoration phase, the engine runs passes sequentially from first_pass through underground_pass, surface_pass, and finally sky_pass and final_pass. If you create a custom ground cover block using a single_block_feature during surface_pass, and you also try to generate a custom tree during surface_pass that checks for that custom ground block beneath it, the order of execution between two rules in the same pass is not guaranteed to go your way unless you chain them as a single aggregate or sequence feature.
To guarantee that custom soil exists before a custom plant checks for it, either place the soil during an earlier pass (such as underground_pass or pregeneration_pass where appropriate) or stop using separate feature rules altogether. The cleaner architectural pattern is to use a single feature rule in surface_pass that triggers a scatter_feature, which in turn points to an aggregate_feature that places the soil first and the plant second at the exact same coordinate.
{
"format_version": "1.13.0",
"minecraft:feature_rules": {
"description": {
"identifier": "wiki:overworld_custom_geode_rule",
"places_feature": "wiki:custom_geode_ore_feature"
},
"conditions": {
"placement_pass": "underground_pass",
"minecraft:biome_filter": [
{
"test": "has_biome_tag",
"operator": "==",
"value": "overworld"
},
{
"test": "has_biome_tag",
"operator": "!=",
"value": "ocean"
}
]
},
"distribution": {
"iterations": 8,
"x": {
"distribution": "uniform",
"extent": [ 0, 16 ]
},
"y": {
"distribution": "triangle",
"extent": [ -60, 20 ]
},
"z": {
"distribution": "uniform",
"extent": [ 0, 16 ]
}
}
}
}Coordinate Evaluation Order and Molang Queries
Another core topic clarified by technical creators and developers is how distribution coordinates and Molang queries interact inside feature rules and scatter features. Inside a distribution block, you define iterations, scatter_chance, and the coordinate axes x, y, and z. However, the engine does not pick all three coordinates at once if you do not want it to; it evaluates them in a specific order, which defaults to x, then z, then y.
Why does the evaluation order of x, z, and y matter? Because of the Molang query query.heightmap(variable.worldx, variable.worldz). When the engine picks an x offset (0 to 16) and a z offset (0 to 16), it immediately updates the internal variables variable.worldx and variable.worldz to reflect the actual world coordinates of that column. Only after x and z are locked in does the engine evaluate the y expression. This is why you can safely write query.heightmap(variable.worldx, variable.worldz) inside the y field: the horizontal coordinates are already known.
If you ever use the coordinate_eval_order property to change the evaluation sequence—for example, evaluating y before x and z—calling query.heightmap in your y expression will return garbage or zero, because variable.worldx and variable.worldz have not been calculated for the current iteration yet. Furthermore, keep in mind that query.heightmap returns the Y coordinate of the air block immediately above the highest light-blocking block (like leaves or water), which is why trees placed purely by heightmap often end up floating on top of existing forest canopies unless your feature explicitly checks the block below.
{
"format_version": "1.13.0",
"minecraft:scatter_feature": {
"description": {
"identifier": "wiki:surface_boulder_scatter"
},
"places_feature": "wiki:mossy_boulder_single",
"iterations": 1,
"scatter_chance": {
"numerator": 1,
"denominator": 12
},
"x": {
"distribution": "uniform",
"extent": [ 0, 16 ]
},
"z": {
"distribution": "uniform",
"extent": [ 0, 16 ]
},
"y": "query.heightmap(variable.worldx, variable.worldz)"
}
}The 3x3 Chunk Neighborhood Limit and Structure Clipping
When placing large custom features—especially structure_template_feature definitions that load .mcstructure files—creators constantly run into half-generated buildings or Content Log warnings about placing blocks outside of a valid area. Understanding the chunk neighborhood budget is essential for fixing this.
During feature generation, the game locks a specific grid of chunks around the chunk currently being decorated. While a feature rule always picks an origin point inside the central 16x16 chunk (provided your x and z extents stay within 0 to 16), the actual blocks placed by the feature are allowed to spill over into the immediate neighboring chunks. However, this spillover radius is strictly limited. If a single structure template or a wide scatter_feature attempts to place blocks beyond the loaded decoration neighborhood, those blocks are either silently dropped or truncated at the chunk boundary.
For large structures that exceed a single chunk in footprint, you have two choices. First, always center your origin or keep the x and z distribution inside the feature rule tightly near the middle of the chunk (for example, extent [4, 12] instead of [0, 16]) to maximize the available buffer space on all four sides before hitting the neighborhood wall. Second, if a structure is massive (such as a dungeon spanning 48x48 blocks or more), do not place it with a single structure_template_feature. Break the .mcstructure into smaller room modules and chain them using a series of offset scatter features, or migrate the build to the data-driven Jigsaw structure system where the engine handles multi-chunk bounding boxes natively.
{
"format_version": "1.13.0",
"minecraft:structure_template_feature": {
"description": {
"identifier": "wiki:ruined_shrine_structure"
},
"structure_name": "mystructure:ruined_shrine",
"adjustment_radius": 4,
"facing_direction": "random",
"constraints": {
"grounded": {},
"unburied": {},
"block_intersection": {
"block_allowlist": [
"minecraft:air",
"minecraft:short_grass",
"minecraft:tall_grass",
"minecraft:snow_layer"
]
}
}
}
}Chaining Logic with Search and Aggregate Features
Because query.heightmap gets stopped by tree leaves and water surfaces, placing features on the true underground floor or the floor of a lush cave requires a different approach. In Q&A discussions, the recommended solution for finding valid surfaces without relying on the sky heightmap is combining a scatter_feature with a search_feature, which then triggers an aggregate_feature or sequence_feature.
A search_feature defines a three-dimensional volume around the input origin and scans along a specified axis (such as top_to_bottom or bottom_to_top) until it finds a location where the target feature's placement rules succeed. For instance, if you want to place stalagmites on cave floors, your feature rule picks a random underground Y coordinate, and the search_feature scans downward up to 16 blocks until the child single_block_feature finds a spot where the target position is air and the block beneath it is stone or deepslate.
By keeping the heavy lifting inside the feature chain rather than running high-iteration Molang loops in the feature rule, you keep chunk generation fast. Bedrock's C++ feature evaluators execute search volumes and block intersection checks much faster than evaluating complex Molang queries hundreds of times per chunk.
{
"format_version": "1.13.0",
"minecraft:single_block_feature": {
"description": {
"identifier": "wiki:cave_crystal_single"
},
"places_block": {
"name": "minecraft:amethyst_cluster",
"states": {
"facing_direction": 1
}
},
"enforce_survivability_rules": false,
"enforce_placement_rules": false,
"may_replace": [
"minecraft:air",
"minecraft:cave_air"
],
"may_attach_to": {
"bottom": [
"minecraft:stone",
"minecraft:deepslate"
]
}
}
}Which versions this applies to
Applies to Minecraft Bedrock 1.21.20 through 1.21.40+ (late 2024). Features and Feature Rules (format_version 1.13.0) are stable and require no experimental toggles. Custom Biomes require the Custom Biomes experimental toggle and remain unstable following the removal of Holiday Creator Features in 1.21.20.
When it does not work
| What you see | Why | The fix |
|---|---|---|
| Feature rule silently does nothing; custom blocks or structures never appear in the world and no error is logged. | The identifier inside the JSON does not match the filename. Unlike entities or blocks, Bedrock's feature and feature_rules loaders require the string after the namespace colon in the identifier to exactly match the JSON file's name (excluding .json). | Rename your file so it matches the identifier's name part. For example, if your identifier is "wiki:overworld_custom_geode_rule", the file must be named "overworld_custom_geode_rule.json". |
| Structures placed by structure_template_feature spawn floating in the air or refuse to spawn in forests. | Using both "grounded": {} and "unburied": {} inside constraints while also restricting block_intersection to only "minecraft:air". If a single blade of grass or leaf block intersects the structure's bounding box, placement fails. | Add common foliage blocks like "minecraft:short_grass", "minecraft:fern", and leaf blocks to the block_allowlist array inside block_intersection, or increase adjustment_radius so the engine can shift the template vertically to find a clean fit. |
| Content Log displays a warning about feature placement out of bounds, or large structures appear sliced in half along a straight line. | Your scatter_feature or feature_rules distribution pushes the placement origin too close to the edge of the chunk (e.g., X or Z near 16), causing a large structure template or ore cluster to spill outside the loaded chunk decoration neighborhood. | Tighten the x and z extent in your feature rule to [4, 12] or [6, 10] so the feature always originates near the center of the chunk, giving it room to expand into adjacent chunks without crossing the unloaded boundary. |
| Custom ores defined with ore_feature do not generate below Y=0 in the Overworld. | Below Y=0, vanilla terrain transitions from "minecraft:stone" to "minecraft:deepslate" and "minecraft:tuff". If your ore_feature replace_rules only list "minecraft:stone" in the may_replace array, the feature fails every block check in the deepslate layer. | Add a second entry to the replace_rules array in your ore_feature (or expand may_replace) to explicitly include "minecraft:deepslate" and "minecraft:tuff". |
Reference, straight from the game files
Read out of the game itself, so the ids match the version you are building for.
